Segmentação de clientes para elaboração de campanhas customizadas de Marketing através da análise RFM.
Esse é um projeto de análise de dados.
Disponível também em meu github.
O conjunto de dados utilizado está disponível neste link.
A BestShop é uma empresa em expansão que comercializa produtos, principalmente alimentícios, através de lojas físicas e de maneira virtual.
Visando otimizar ainda mais suas vendas, a empresa contratou um consultor em ciência de dados para realizar a segmentação de seus clientes. O principal objetivo desse projeto é identificar padrões de consumo para realizar campanhas de Marketing customizadas e consequentemente mais assertivas, visto que atualmente, as taxas de conversão das campanhas são baixas.
Durante a reunião de negócios, vários métodos de como realizar a segmentação dos clientes foram debatidos. Ao final, foi decidido que para o primeiro ciclo do projeto, o cientista de dados deverá utilizar a análise RFM e assim, sugerir recomendações de ações para o time de Marketing.
Em relação ao conjunto de dados, as seguintes informações foram disponibilizadas:
Categoria | Variáveis | Descrição |
---|---|---|
Clientes | ID | Identificação do cliente |
Year_Birth | Ano de nascimento | |
Education | Nível educacional | |
Marital_Status | Estado civil | |
Income | Renda familiar anual | |
Kidhome | Número de crianças que moram na casa | |
Teenhome | Número de adolescentes que moram na casa | |
Dt_Customer | Data que o consumidor se inscreveu na loja | |
Recency | Número de dias desde a última compra do cliente | |
Complain | 1 se o cliente reclamar nos últimos 2 anos, 0 caso contrário | |
Produtos | MntWines | Quantidade gasta em vinho nos últimos 2 anos |
MntFruits | Quantidade gasta com frutas nos últimos 2 anos | |
MntMeatProducts | Quantidade gasta com carne nos últimos 2 anos | |
MntFishProduct | Quantidade gasta em peixe nos últimos 2 anos | |
MntSweetProducts | Quantia gasta em doces nos últimos 2 anos | |
MntGoldProds | Quantia gasta em ouro nos últimos 2 anos | |
Promoção | NumDealsPurchases | Número de compras feitas com desconto |
AcceptedCmp1 | 1 se o cliente aceitou a oferta na 1° campanha, 0 caso contrário | |
AcceptedCmp2 | 1 se o cliente aceitou a oferta na 2° campanha, 0 caso contrário | |
AcceptedCmp3 | 1 se o cliente aceitou a oferta na 3° campanha, 0 caso contrário | |
AcceptedCmp4 | 1 se o cliente aceitou a oferta na 4° campanha, 0 caso contrário | |
AcceptedCmp5 | 1 se o cliente aceitou a oferta na 5° campanha, 0 caso contrário | |
Response | 1 se o cliente aceitou a oferta na última campanha, 0 caso contrário | |
Local de Compra | NumWebPurchases | Número de compras feitas por meio do site da empresa |
NumCatalogPurchases | Número de compras feitas usando um catálogo | |
NumStorePurchases | Número de compras feitas diretamente nas lojas | |
NumWebVisitsMonth | Número de visitas ao site da empresa no último mês | |
Como estratégia para a solução do projeto, definimos as seguintes etapas:
Vamos iniciar o projeto carregando as bibliotecas necessárias e os conjuntos de dados.
# Filtragem das mensagens de avisos.
import warnings
warnings.filterwarnings('ignore')
# Manipulação de dados.
from datetime import datetime
import numpy as np
import pandas as pd
# Criação de gráficos.
import matplotlib.pyplot as plt
import seaborn as sns
# Versão da linguagem Python.
from platform import python_version
print('Versão da linguagem Python:', python_version())
Versão da linguagem Python: 3.9.12
# Versão dos pacotes.
%reload_ext watermark
%watermark --iversions
numpy : 1.21.5 seaborn : 0.11.2 matplotlib: 3.5.1 pandas : 1.4.2
# Configurações do notebook.
# Plotagens.
from matplotlib import rcParams
rcParams['figure.figsize'] = 15, 10
rcParams['lines.linewidth'] = 1
# Estilo dos gráficos.
plt.style.use('ggplot')
# Configuração Dataframe.
pd.set_option('display.max_columns', None)
Nessa etapa, nosso objetivo é realizar uma análise geral nos dados a fim de tratar possíveis inconsistências.
# Carregando o conjunto de dados.
df = pd.read_csv('data/marketing_campaign.csv', sep = ';')
Criar uma cópia do dataset é uma boa prática para não perdermos o conteúdo original durante a manipulação dos dados.
# Cópia do dataset.
df1 = df
# Visualizando o conjunto de dados.
df1.head()
ID | Year_Birth | Education | Marital_Status | Income | Kidhome | Teenhome | Dt_Customer | Recency | MntWines | MntFruits | MntMeatProducts | MntFishProducts | MntSweetProducts | MntGoldProds | NumDealsPurchases | NumWebPurchases | NumCatalogPurchases | NumStorePurchases | NumWebVisitsMonth | AcceptedCmp3 | AcceptedCmp4 | AcceptedCmp5 | AcceptedCmp1 | AcceptedCmp2 | Complain | Z_CostContact | Z_Revenue | Response | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 5524 | 1957 | Graduation | Single | 58138.0 | 0 | 0 | 2012-09-04 | 58 | 635 | 88 | 546 | 172 | 88 | 88 | 3 | 8 | 10 | 4 | 7 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 11 | 1 |
1 | 2174 | 1954 | Graduation | Single | 46344.0 | 1 | 1 | 2014-03-08 | 38 | 11 | 1 | 6 | 2 | 1 | 6 | 2 | 1 | 1 | 2 | 5 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 11 | 0 |
2 | 4141 | 1965 | Graduation | Together | 71613.0 | 0 | 0 | 2013-08-21 | 26 | 426 | 49 | 127 | 111 | 21 | 42 | 1 | 8 | 2 | 10 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 11 | 0 |
3 | 6182 | 1984 | Graduation | Together | 26646.0 | 1 | 0 | 2014-02-10 | 26 | 11 | 4 | 20 | 10 | 3 | 5 | 2 | 2 | 0 | 4 | 6 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 11 | 0 |
4 | 5324 | 1981 | PhD | Married | 58293.0 | 1 | 0 | 2014-01-19 | 94 | 173 | 43 | 118 | 46 | 27 | 15 | 5 | 5 | 3 | 6 | 5 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 11 | 0 |
# Dimensão do dataframe.
print('Número de registros: {}'.format(df1.shape[0]))
print('Número de variáveis: {}'.format(df1.shape[1]))
Número de registros: 2240 Número de variáveis: 29
# Informações do dataframe.
df1.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 2240 entries, 0 to 2239 Data columns (total 29 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ID 2240 non-null int64 1 Year_Birth 2240 non-null int64 2 Education 2240 non-null object 3 Marital_Status 2240 non-null object 4 Income 2216 non-null float64 5 Kidhome 2240 non-null int64 6 Teenhome 2240 non-null int64 7 Dt_Customer 2240 non-null object 8 Recency 2240 non-null int64 9 MntWines 2240 non-null int64 10 MntFruits 2240 non-null int64 11 MntMeatProducts 2240 non-null int64 12 MntFishProducts 2240 non-null int64 13 MntSweetProducts 2240 non-null int64 14 MntGoldProds 2240 non-null int64 15 NumDealsPurchases 2240 non-null int64 16 NumWebPurchases 2240 non-null int64 17 NumCatalogPurchases 2240 non-null int64 18 NumStorePurchases 2240 non-null int64 19 NumWebVisitsMonth 2240 non-null int64 20 AcceptedCmp3 2240 non-null int64 21 AcceptedCmp4 2240 non-null int64 22 AcceptedCmp5 2240 non-null int64 23 AcceptedCmp1 2240 non-null int64 24 AcceptedCmp2 2240 non-null int64 25 Complain 2240 non-null int64 26 Z_CostContact 2240 non-null int64 27 Z_Revenue 2240 non-null int64 28 Response 2240 non-null int64 dtypes: float64(1), int64(25), object(3) memory usage: 507.6+ KB
# Registros ausentes.
# Obtendo os dados.
valores_ausentes = df1.isnull().sum()
contatem_total = df1.isnull().count()
porcentagem = round(valores_ausentes / contatem_total * 100, 2)
# Criando o dataframe.
df_ausentes = pd.DataFrame({'Total de Valores Ausentes': valores_ausentes, 'Porcentagem': porcentagem})
df_ausentes
Total de Valores Ausentes | Porcentagem | |
---|---|---|
ID | 0 | 0.00 |
Year_Birth | 0 | 0.00 |
Education | 0 | 0.00 |
Marital_Status | 0 | 0.00 |
Income | 24 | 1.07 |
Kidhome | 0 | 0.00 |
Teenhome | 0 | 0.00 |
Dt_Customer | 0 | 0.00 |
Recency | 0 | 0.00 |
MntWines | 0 | 0.00 |
MntFruits | 0 | 0.00 |
MntMeatProducts | 0 | 0.00 |
MntFishProducts | 0 | 0.00 |
MntSweetProducts | 0 | 0.00 |
MntGoldProds | 0 | 0.00 |
NumDealsPurchases | 0 | 0.00 |
NumWebPurchases | 0 | 0.00 |
NumCatalogPurchases | 0 | 0.00 |
NumStorePurchases | 0 | 0.00 |
NumWebVisitsMonth | 0 | 0.00 |
AcceptedCmp3 | 0 | 0.00 |
AcceptedCmp4 | 0 | 0.00 |
AcceptedCmp5 | 0 | 0.00 |
AcceptedCmp1 | 0 | 0.00 |
AcceptedCmp2 | 0 | 0.00 |
Complain | 0 | 0.00 |
Z_CostContact | 0 | 0.00 |
Z_Revenue | 0 | 0.00 |
Response | 0 | 0.00 |
A variável Income
possui 24 valores ausentes, o que representa pouco mais de 1% dos dados.
Como são poucos registros, podemos excluí-los da análise.
# Dimensão original.
shape_original = df1.shape
print('Antes: ', shape_original)
# Excluindo os registros inválidos.
df1.dropna(subset = ['Income'], inplace = True)
# Dimensão após o tratamento.
shape_tratado = df1.shape
print('Depois: ', shape_tratado)
Antes: (2240, 29) Depois: (2216, 29)
# Registros únicos de cada variável.
for col in df1:
print(f'''Registros únicos da Variável {col}:''')
print(df1[col].nunique())
print()
Registros únicos da Variável ID: 2216 Registros únicos da Variável Year_Birth: 59 Registros únicos da Variável Education: 5 Registros únicos da Variável Marital_Status: 8 Registros únicos da Variável Income: 1974 Registros únicos da Variável Kidhome: 3 Registros únicos da Variável Teenhome: 3 Registros únicos da Variável Dt_Customer: 662 Registros únicos da Variável Recency: 100 Registros únicos da Variável MntWines: 776 Registros únicos da Variável MntFruits: 158 Registros únicos da Variável MntMeatProducts: 554 Registros únicos da Variável MntFishProducts: 182 Registros únicos da Variável MntSweetProducts: 176 Registros únicos da Variável MntGoldProds: 212 Registros únicos da Variável NumDealsPurchases: 15 Registros únicos da Variável NumWebPurchases: 15 Registros únicos da Variável NumCatalogPurchases: 14 Registros únicos da Variável NumStorePurchases: 14 Registros únicos da Variável NumWebVisitsMonth: 16 Registros únicos da Variável AcceptedCmp3: 2 Registros únicos da Variável AcceptedCmp4: 2 Registros únicos da Variável AcceptedCmp5: 2 Registros únicos da Variável AcceptedCmp1: 2 Registros únicos da Variável AcceptedCmp2: 2 Registros únicos da Variável Complain: 2 Registros únicos da Variável Z_CostContact: 1 Registros únicos da Variável Z_Revenue: 1 Registros únicos da Variável Response: 2
As variáveis Z_CostContact
e Z_Revenue
possuem apenas um valor único, sendo assim, iremos excluí-las.
# Excluindo as variáveis.
df1 = df1.drop(['Z_CostContact', 'Z_Revenue'], axis = 1)
# Cópia do dataframe.
df2 = df1.copy()
Realizamos uma análise geral nos dados tratando as principais inconsistências observadas, agora, partiremos para uma análise mais detalhada.
Baseado no dicionário de dados, vamos separar as variáveis.
# Identificação dos clientes.
id_clientes = ['ID']
# Variável periódica.
data = ['Dt_Customer']
# Convertendo o tipo da variável.
df2['Dt_Customer'] = pd.to_datetime(df2['Dt_Customer'])
# Variáveis numéricas.
nums = ['Year_Birth',
'Income',
'Kidhome',
'Teenhome',
'Recency',
'MntWines',
'MntFruits',
'MntMeatProducts',
'MntFishProducts',
'MntSweetProducts',
'MntGoldProds',
'NumDealsPurchases',
'NumWebPurchases',
'NumCatalogPurchases',
'NumStorePurchases',
'NumWebVisitsMonth']
# Variáveis categóricas.
cats = ['Education',
'Marital_Status',
'AcceptedCmp1',
'AcceptedCmp2',
'AcceptedCmp3',
'AcceptedCmp4',
'AcceptedCmp5',
'Complain',
'Response']
# Total de variáveis nas listas.
len(id_clientes + data + nums + cats)
27
# Datas do conjunto de dados.
print('Data início: {}'.format(df2['Dt_Customer'].min()))
print('Data final: {}'.format(df2['Dt_Customer'].max()))
Data início: 2012-07-30 00:00:00 Data final: 2014-06-29 00:00:00
A base de dados possui registros de clientes que se inscreveram entre as datas de 30/07/2012 a 29/06/2014.
# Estatísticas das variáveis.
df2[nums].describe().T
count | mean | std | min | 25% | 50% | 75% | max | |
---|---|---|---|---|---|---|---|---|
Year_Birth | 2216.0 | 1968.820397 | 11.985554 | 1893.0 | 1959.0 | 1970.0 | 1977.00 | 1996.0 |
Income | 2216.0 | 52247.251354 | 25173.076661 | 1730.0 | 35303.0 | 51381.5 | 68522.00 | 666666.0 |
Kidhome | 2216.0 | 0.441787 | 0.536896 | 0.0 | 0.0 | 0.0 | 1.00 | 2.0 |
Teenhome | 2216.0 | 0.505415 | 0.544181 | 0.0 | 0.0 | 0.0 | 1.00 | 2.0 |
Recency | 2216.0 | 49.012635 | 28.948352 | 0.0 | 24.0 | 49.0 | 74.00 | 99.0 |
MntWines | 2216.0 | 305.091606 | 337.327920 | 0.0 | 24.0 | 174.5 | 505.00 | 1493.0 |
MntFruits | 2216.0 | 26.356047 | 39.793917 | 0.0 | 2.0 | 8.0 | 33.00 | 199.0 |
MntMeatProducts | 2216.0 | 166.995939 | 224.283273 | 0.0 | 16.0 | 68.0 | 232.25 | 1725.0 |
MntFishProducts | 2216.0 | 37.637635 | 54.752082 | 0.0 | 3.0 | 12.0 | 50.00 | 259.0 |
MntSweetProducts | 2216.0 | 27.028881 | 41.072046 | 0.0 | 1.0 | 8.0 | 33.00 | 262.0 |
MntGoldProds | 2216.0 | 43.965253 | 51.815414 | 0.0 | 9.0 | 24.5 | 56.00 | 321.0 |
NumDealsPurchases | 2216.0 | 2.323556 | 1.923716 | 0.0 | 1.0 | 2.0 | 3.00 | 15.0 |
NumWebPurchases | 2216.0 | 4.085289 | 2.740951 | 0.0 | 2.0 | 4.0 | 6.00 | 27.0 |
NumCatalogPurchases | 2216.0 | 2.671029 | 2.926734 | 0.0 | 0.0 | 2.0 | 4.00 | 28.0 |
NumStorePurchases | 2216.0 | 5.800993 | 3.250785 | 0.0 | 3.0 | 5.0 | 8.00 | 13.0 |
NumWebVisitsMonth | 2216.0 | 5.319043 | 2.425359 | 0.0 | 3.0 | 6.0 | 7.00 | 20.0 |
# Histogramas das variáveis.
# Especificando as variáveis.
features = nums
# Plotagem.
for i in range(0, len(nums)):
plt.subplot(4, 4, i + 1)
sns.histplot(x = df2[features[i]], color = 'orange')
plt.xlabel(features[i])
plt.tight_layout()
# Boxplots das variáveis.
# Especificando as variáveis.
features = nums
# Plotagem.
for col in range(0, len(features)):
plt.subplot(4, 4, col + 1)
sns.boxplot(y = df2[features[col]], color = 'orange')
plt.tight_layout()
Iremos tratar as seguintes variáveis:
Year_Birth
possui registros muito antigos.Income
possui valores outliers, ou seja, valores muito distantes da média.# Contagem de registros antes do tratamento.
print(f'Número de linhas antes de filtrar valores extremos (outliers): {len(df2)}')
Número de linhas antes de filtrar valores extremos (outliers): 2216
# Limite de corte para variável "Income".
limite = df2['Income'].mean() + 3 * df2['Income'].std()
print('Desconsiderar valores acima de:', limite)
Desconsiderar valores acima de: 127766.48133649485
# Tratando os valores outliers.
df2 = df2[(df2['Year_Birth'] >= 1922) & (df2['Income'] <= limite)]
# Contagem de registros após o tratamento.
print(f'Número de linhas após filtrar valores extremos (outliers): {len(df2)}')
Número de linhas após filtrar valores extremos (outliers): 2205
# Variáveis após o tratamento.
# Plotagem.
plt.subplot(2, 2, 1)
p1 = sns.boxplot(y = df2['Year_Birth'], color = 'orange')
plt.subplot(2, 2, 2)
p2 = sns.boxplot(y = df2['Income'], color = 'orange')
plt.subplot(2, 2, 3)
p3 = sns.histplot(df2['Year_Birth'], color = 'orange')
p3.set_ylabel('')
plt.subplot(2, 2, 4)
p4 = sns.histplot(df2['Income'], color = 'orange')
p4.set_ylabel('')
# Título.
p1.text(x = 0.1,
y = 2005,
s = 'Variáveis Após o Tratamento de Outliers',
fontsize = 20);
# Análise univariada.
# Especificando as variáveis.
features = cats
# Plotagem.
for i in range(0, len(features)):
plt.subplot(5, 2, i + 1)
sns.countplot(x = df2[cats[i]], palette = 'autumn_r')
plt.tight_layout()
# Análise bivariada ("Response").
# Especificando as variáveis.
features = cats[:-1]
# Plotagem.
for i in range(0, len(features)):
plt.subplot(5, 2, i + 1)
sns.countplot(data = df2,
x = features[i],
hue = 'Response',
palette = 'autumn_r')
plt.tight_layout()
# Cópia do dataframe.
df3 = df2.copy()
As seguintes variáveis serão criadas de acordo com as regras abaixo:
Age
informa a idade do cliente.Group_Age
informa a que grupo o cliente pertence conforme sua idade. Graduate
informa se o cliente possui ensino básico ou superior.Relationship
informa se o cliente é solteiro ou casado.Kids
informa a quantidade de filhos do cliente (Kidhome + Teenhome).CurrentDay
será utilizada para o calcular o tempo de vida do cliente. CustomerMonth
informa o tempo de vida do cliente (em meses). TotalProd
total de produtos vendidos.TotalPurchases
total de vendas realizadas.TotalPromo
informa a quantidade de campanhas de marketing que o cliente aceitou. # Variável "Age".
df3.insert(2, 'Age', 2015 - df3['Year_Birth'])
# Informações da variável.
print('A menor idade é: {} anos'.format(df3['Age'].min()))
print('A maior idade é: {} anos'.format(df3['Age'].max()))
A menor idade é: 19 anos A maior idade é: 75 anos
# Variável "GroupAge".
# Definindo os valores e labels.
bins_age = [18, 25, 40, 60, 100]
labels_age = [1, 2, 3, 4]
# Criando a variável.
df3.insert(3, 'GroupAge', pd.cut(df3['Age'], bins_age, labels = labels_age).astype('int64'))
# Variável "Graduate".
df3.insert(5, 'Graduate', df3['Education'].apply(lambda x: 'Básico' if x == 'Basic' else 'Superior'))
# Variável "Relationship".
df3.insert(7, 'Relationship', df3['Marital_Status'].copy())
# Substituindo os valores.
df3['Relationship'] = df3['Relationship'].replace(['Single', 'Divorced', 'Widow', 'Alone', 'Absurd', 'YOLO'], 'Solteiro')
df3['Relationship'] = df3['Relationship'].replace(['Married', 'Together'], 'Casado')
# Variável "Kids".
df3.insert(11, 'Kids', df3['Kidhome'] + df3['Teenhome'])
# Variável "CurrentDay".
df3.insert(13, 'CurrentDay', datetime.strptime('2014-07', '%Y-%m'))
# Variável "CustomerMonth".
df3.insert(14, 'CustomerMonth', ((df3['CurrentDay'] - df3['Dt_Customer']) / 30).apply(lambda x: x.days).astype('int64'))
# Variável "TotalProd".
df3.insert(22, 'TotalProd',
df3['MntWines'] +
df3['MntFruits'] +
df3['MntMeatProducts'] +
df3['MntFishProducts'] +
df3['MntSweetProducts'] +
df3['MntGoldProds'])
# Variável "TotalPurchases".
df3.insert(27, 'TotalPurchases',
df3['NumDealsPurchases'] +
df3['NumWebPurchases'] +
df3['NumCatalogPurchases'] +
df3['NumStorePurchases'])
# Variável "TotalPromo".
df3.insert(36, 'TotalPromo',
df3['AcceptedCmp1'] +
df3['AcceptedCmp2'] +
df3['AcceptedCmp3'] +
df3['AcceptedCmp4'] +
df3['AcceptedCmp5'] +
df3['Response'])
# Cópia do dataframe.
df4 = df3.copy()
# Excluindo as variáveis com informações duplicadas.
df4 = df4.drop(['Year_Birth',
'Education',
'Marital_Status',
'Kidhome',
'Teenhome',
'Dt_Customer',
'CurrentDay',
'AcceptedCmp1',
'AcceptedCmp2',
'AcceptedCmp3',
'AcceptedCmp4',
'AcceptedCmp5',
'Response'], axis = 1)
# Visualizando o dataframe.
df4.head()
ID | Age | GroupAge | Graduate | Relationship | Income | Kids | CustomerMonth | Recency | MntWines | MntFruits | MntMeatProducts | MntFishProducts | MntSweetProducts | MntGoldProds | TotalProd | NumDealsPurchases | NumWebPurchases | NumCatalogPurchases | NumStorePurchases | TotalPurchases | NumWebVisitsMonth | Complain | TotalPromo | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 5524 | 58 | 3 | Superior | Solteiro | 58138.0 | 0 | 22 | 58 | 635 | 88 | 546 | 172 | 88 | 88 | 1617 | 3 | 8 | 10 | 4 | 25 | 7 | 0 | 1 |
1 | 2174 | 61 | 4 | Superior | Solteiro | 46344.0 | 2 | 3 | 38 | 11 | 1 | 6 | 2 | 1 | 6 | 27 | 2 | 1 | 1 | 2 | 6 | 5 | 0 | 0 |
2 | 4141 | 50 | 3 | Superior | Casado | 71613.0 | 0 | 10 | 26 | 426 | 49 | 127 | 111 | 21 | 42 | 776 | 1 | 8 | 2 | 10 | 21 | 4 | 0 | 0 |
3 | 6182 | 31 | 2 | Superior | Casado | 26646.0 | 1 | 4 | 26 | 11 | 4 | 20 | 10 | 3 | 5 | 53 | 2 | 2 | 0 | 4 | 8 | 6 | 0 | 0 |
4 | 5324 | 34 | 2 | Superior | Casado | 58293.0 | 1 | 5 | 94 | 173 | 43 | 118 | 46 | 27 | 15 | 422 | 5 | 5 | 3 | 6 | 19 | 5 | 0 | 0 |
Agora que entendemos o que as variáveis representam, vamos criar algumas perguntas e hipóteses de negócio.
# Pergunta 1.
# Especificando as variáveis.
prod = ['MntWines',
'MntFruits',
'MntMeatProducts',
'MntFishProducts',
'MntSweetProducts',
'MntGoldProds']
# Plotagem.
df4[prod].sum().sort_values(ascending = False).plot.bar(color = 'orange')
plt.title('Produtos Mais Vendidos \n', fontdict = {'fontsize':20})
plt.xticks(rotation = 60);
# Pergunta 2.
# Matriz de correlação.
corr_df = df4[['MntWines',
'MntFruits',
'MntMeatProducts',
'MntFishProducts',
'MntSweetProducts',
'MntGoldProds']].corr()
# Plotagem.
p1 = sns.heatmap(corr_df,
cmap = 'Spectral',
annot = True,
fmt = '.2f')
p1.set_title('Análise de Correlação \n', size = 20);
# Pergunta 3.
# Utilizaremos o df3 para responder essa questão.
# Especificando as variáveis.
mkt = ['AcceptedCmp1',
'AcceptedCmp2',
'AcceptedCmp3',
'AcceptedCmp4',
'AcceptedCmp5',
'Response']
# Plotagem.
df3[mkt].sum().plot.bar(color = 'orange')
plt.title('Conversão de Clientes por Campanha\n', fontdict = {'fontsize':20})
plt.xticks(rotation = 60);
Response
sendo a 6º campanha realizada, essa foi a de maior aceitação entre os clientes.# Pergunta 4.
# Especificando as variáveis.
local = ['NumWebPurchases',
'NumCatalogPurchases',
'NumStorePurchases']
# Plotagem.
df4[local].sum().sort_values(ascending = False).plot.bar(color = 'orange')
plt.title('Vendas por Local \n', fontdict = {'fontsize':20})
plt.xticks(rotation = 60);
# Hipótese 1.
# Preparando os dados.
h1 = df4[['GroupAge', 'NumWebPurchases']].groupby('GroupAge').sum().reset_index()
h1_1 = df4[['GroupAge', 'NumWebPurchases']].groupby('GroupAge').mean().reset_index()
# Plotagem.
plt.subplot(1, 2, 1)
p1 = sns.barplot(x = 'GroupAge', y = 'NumWebPurchases', data = h1, palette = 'autumn_r')
p1.set_title('Vendas Totais', size = 15)
plt.xticks(rotation = 60)
plt.subplot(1, 2, 2)
p2 = sns.barplot(x = 'GroupAge', y = 'NumWebPurchases', data = h1_1, palette = 'autumn_r')
p2.set_title('Média de Vendas', size = 15)
plt.xticks(rotation = 60)
# Título.
p1.text(x = 2,
y = 5500,
s = 'Vendas pela Internet por Grupo de Idade',
fontsize = 20);
# Hipótese 2
# Preparando os dados.
h2 = df4[['Graduate', 'TotalProd']].groupby('Graduate').sum().reset_index()
h2_1 = df4[['Graduate', 'TotalProd']].groupby('Graduate').mean().reset_index()
# Plotagem.
plt.subplot(1, 2, 1)
p1 = sns.barplot(x = 'Graduate', y = 'TotalProd', data = h2, palette = 'autumn_r')
p1.set_title('Vendas Totais', size = 15);
plt.subplot(1, 2, 2)
p2 = sns.barplot(x = 'Graduate', y = 'TotalProd', data = h2_1, palette = 'autumn_r')
p2.set_title('Média de Vendas', size = 15)
# Título.
p1.text(x = 1,
y = 1500000,
s = 'Vendas por Nível Educacional',
fontsize = 20);
# Hipótese 3
# Preparando os dados.
h3 = df4[['Relationship', 'TotalProd']].groupby('Relationship').sum().reset_index()
h3_1 = df4[['Relationship', 'TotalProd']].groupby('Relationship').mean().reset_index()
# Plotagem.
plt.subplot(1, 2, 1)
p1 = sns.barplot(x = 'Relationship', y = 'TotalProd', data = h3, palette = 'autumn_r')
p1.set_title('Vendas Totais', size = 15);
plt.subplot(1, 2, 2)
p2 = sns.barplot(x = 'Relationship', y = 'TotalProd', data = h3_1, palette = 'autumn_r')
p2.set_title('Média de Vendas', size = 15)
# Título.
p1.text(x = 1.0,
y = 950000,
s = 'Vendas por Estado Civil',
fontsize = 20);
# Hipótese 4.
# Preparação dos dados.
h4 = df4[['Income', 'TotalProd']].groupby('Income').sum().reset_index()
# Plotagem.
plt.subplot(1, 2, 1)
p1 = sns.scatterplot(x = 'Income', y = 'TotalProd', data = h4, color = 'orange')
p1.set_title('Gráfico de Distribuição', size = 15)
plt.subplot(1, 2, 2)
p2 = sns.heatmap(h4.corr(method = 'pearson'), annot = True)
p2.set_title('Análise de Correlação', size = 15)
# Título.
p1.text(x = 90000,
y = 5800,
s = 'Vendas por Renda Anual',
fontsize = 20);
# Hipótese 5
# Preparando os dados.
h5 = df4[['Kids', 'TotalProd']].groupby('Kids').sum().reset_index()
h5_1 = df4[['Kids', 'TotalProd']].groupby('Kids').mean().reset_index()
# Plotagem.
plt.subplot(1, 2, 1)
p1 = sns.barplot(x = 'Kids', y = 'TotalProd', data = h5, palette = 'autumn_r')
p1.set_title('Vendas Totais', size = 15);
plt.subplot(1, 2, 2)
p2 = sns.barplot(x = 'Kids', y = 'TotalProd', data = h5_1, palette = 'autumn_r')
p2.set_title('Média de Vendas', size = 15)
# Título.
p1.text(x = 2.5,
y = 800000,
s = 'Vendas por Número de Filhos',
fontsize = 20);
# Hipótese 6.
# Preparando os dados.
h6 = df4[['CustomerMonth', 'TotalProd']].groupby('CustomerMonth').sum().reset_index()
# Plotagem.
plt.subplot(1,2,1)
p1 = sns.barplot(x = 'CustomerMonth', y = 'TotalProd', data = h6, palette = 'autumn_r')
plt.xticks(rotation = 60);
plt.subplot(1,2,2)
p2 = sns.heatmap(h6.corr(method = 'pearson'), annot = True)
plt.xticks(rotation = 60);
# Título.
p1.text(x = 15,
y = 90000,
s = 'Vendas por Tempo de Vida do Cliente',
fontsize = 20);
A análise RFM é uma estratégia para estimar o valor de um cliente, com base em três variáveis:
Para o cálculo da RFM precisamos saber o preço unitário dos produtos.
Para isso, marcamos uma reunião com a área de negócio onde foi informado os seguintes valores:
# Definindo os preços dos produtos.
vinho = 15
fruta = 5
carne = 20
peixe = 20
doce = 10
ouro = 30
# Variável "Total$".
df4.insert(14, 'Total$',
(df4['MntWines'] * vinho) +
(df4['MntFruits'] * fruta) +
(df4['MntMeatProducts'] * carne) +
(df4['MntFishProducts'] * peixe) +
(df4['MntSweetProducts'] * doce) +
(df4['MntGoldProds'] * ouro))
# Visualizando as variáveis
df4[['MntWines',
'MntFruits',
'MntMeatProducts',
'MntFishProducts',
'MntSweetProducts',
'MntGoldProds',
'Total$']].head()
MntWines | MntFruits | MntMeatProducts | MntFishProducts | MntSweetProducts | MntGoldProds | Total$ | |
---|---|---|---|---|---|---|---|
0 | 635 | 88 | 546 | 172 | 88 | 88 | 27845 |
1 | 11 | 1 | 6 | 2 | 1 | 6 | 520 |
2 | 426 | 49 | 127 | 111 | 21 | 42 | 12865 |
3 | 11 | 4 | 20 | 10 | 3 | 5 | 965 |
4 | 173 | 43 | 118 | 46 | 27 | 15 | 6810 |
# Dataframe para a análise RFM.
df_seg = df4[['ID',
'Recency',
'TotalPurchases',
'Total$']]
# Renomeando as colunas.
df_seg.rename(columns = {'Recency': 'R',
'TotalPurchases': 'F',
'Total$': 'M'}, inplace = True)
# Visualizando o dataframe.
df_seg.head()
ID | R | F | M | |
---|---|---|---|---|
0 | 5524 | 58 | 25 | 27845 |
1 | 2174 | 38 | 6 | 520 |
2 | 4141 | 26 | 21 | 12865 |
3 | 6182 | 26 | 8 | 965 |
4 | 5324 | 94 | 19 | 6810 |
-R (Recência): indica quantos dias se passaram desde a última compra do cliente.
-F (Frequência): indica a quantidade de compras realizadas pelo cliente.
-M (Monetário): indica a quantidade monetária gasta pelo cliente.
# Densidade das variáveis RFM.
# Plotagem.
plt.subplot(3, 1, 1)
p1 = sns.distplot(df_seg['R'], color = 'orange')
plt.subplot(3, 1, 2)
p2 = sns.distplot(df_seg['F'], color = 'orange')
plt.subplot(3, 1, 3)
p3 = sns.distplot(df_seg['M'], color = 'orange')
# Título.
p1.text(x = 25,
y = 0.015,
s = 'Densidade das Variáveis RFM',
fontsize = 20);
Com os valores RFM definidos podemos iniciar a segmentação. Vamos dividir os dados em quatro quartis.
# Divisão do dataframe.
quantiles = df_seg.quantile(q = [0.25, 0.5, 0.75, 1])
quantiles
ID | R | F | M | |
---|---|---|---|---|
0.25 | 2815.0 | 24.0 | 8.0 | 1290.0 |
0.50 | 5455.0 | 49.0 | 15.0 | 6860.0 |
0.75 | 8418.0 | 74.0 | 21.0 | 17720.0 |
1.00 | 11191.0 | 99.0 | 43.0 | 43675.0 |
Acima temos os valores máximos da RFM para cada quartil.
Porém não faz sentido entregar essa divisão para a empresa, pois o R deve ser baixo, indicando que o cliente está ativo na empresa, e o F e M devem ser altos, pois indicam a frequência e o valor total gasto.
Sendo assim, iremos definir duas funções que irão realizar essa filtragem.
# Convertendo o dataframe.
quantiles = quantiles.to_dict()
Com a função R_Score, iremos classificar os valores de "R".
Os valores serão classificados em grupos de 1 a 4, onde o grupo 1 receberá os menores valores de R, ou seja, os melhores valores, até o grupo 4, que receberá os valores mais altos de R.
# Função para classificar o "R".
def R_Score(a, b, c):
if a <= c[b][0.25]:
return 1
elif a <= c[b][0.50]:
return 2
elif a <= c[b][0.75]:
return 3
else:
return 4
Para a função FM_Score, a lógica é a mesma da R_Score.
Porém aqui, desejamos obter os maiores valores, sendo assim, esses valores serão classificados como 1, e os menores valores como 4.
# Função para classificar o "F" e o "M".
def FM_Score(x, y, z):
if x <= z[y][0.25]:
return 4
elif x <= z[y][0.50]:
return 3
elif x <= z[y][0.75]:
return 2
else:
return 1
# Classificando valores de "R".
df_seg['R_Score'] = df_seg['R'].apply(R_Score, args = ('R', quantiles))
# Classificando valores de "F".
df_seg['F_Score'] = df_seg['F'].apply(FM_Score, args = ('F', quantiles))
# Classificando valores de "M".
df_seg['M_Score'] = df_seg['M'].apply(FM_Score, args = ('M', quantiles))
# Visualizando o dataframe.
df_seg.head()
ID | R | F | M | R_Score | F_Score | M_Score | |
---|---|---|---|---|---|---|---|
0 | 5524 | 58 | 25 | 27845 | 3 | 1 | 1 |
1 | 2174 | 38 | 6 | 520 | 2 | 4 | 4 |
2 | 4141 | 26 | 21 | 12865 | 2 | 2 | 2 |
3 | 6182 | 26 | 8 | 965 | 2 | 4 | 4 |
4 | 5324 | 94 | 19 | 6810 | 4 | 2 | 3 |
Com esse dataframe, já podemos filtrar os clientes de acordo com o Score desejado.
Para facilitar, vamos criar uma variável concatenando os resultados.
# Variável "RFM_Score".
df_seg['RFM_Score'] = df_seg['R_Score'].astype(str) + df_seg['F_Score'].astype(str) + df_seg['M_Score'].astype(str)
df_seg.head()
ID | R | F | M | R_Score | F_Score | M_Score | RFM_Score | |
---|---|---|---|---|---|---|---|---|
0 | 5524 | 58 | 25 | 27845 | 3 | 1 | 1 | 311 |
1 | 2174 | 38 | 6 | 520 | 2 | 4 | 4 | 244 |
2 | 4141 | 26 | 21 | 12865 | 2 | 2 | 2 | 222 |
3 | 6182 | 26 | 8 | 965 | 2 | 4 | 4 | 244 |
4 | 5324 | 94 | 19 | 6810 | 4 | 2 | 3 | 423 |
# Total de grupos criados.
count = df_seg['RFM_Score'].unique()
print(count)
len(count)
['311' '244' '222' '423' '112' '233' '144' '344' '421' '333' '111' '133' '411' '443' '211' '322' '122' '123' '121' '212' '433' '444' '312' '321' '343' '221' '412' '434' '223' '132' '243' '231' '422' '323' '232' '143' '131' '334' '134' '332' '234' '432' '431' '331' '142' '113']
46
Visando diminuir o número de grupos criados na etapa anterior, utilizaremos apenas os scores de recência e frequência.
# Variável "RF_Score".
df_seg['RF_Score'] = df_seg['R_Score'].astype(str) + df_seg['F_Score'].astype(str)
# Visualizando o dataframe.
df_seg.head()
ID | R | F | M | R_Score | F_Score | M_Score | RFM_Score | RF_Score | |
---|---|---|---|---|---|---|---|---|---|
0 | 5524 | 58 | 25 | 27845 | 3 | 1 | 1 | 311 | 31 |
1 | 2174 | 38 | 6 | 520 | 2 | 4 | 4 | 244 | 24 |
2 | 4141 | 26 | 21 | 12865 | 2 | 2 | 2 | 222 | 22 |
3 | 6182 | 26 | 8 | 965 | 2 | 4 | 4 | 244 | 24 |
4 | 5324 | 94 | 19 | 6810 | 4 | 2 | 3 | 423 | 42 |
Os grupos serão criados seguindo as regras abaixo:
# Definindo as regras de negócio.
seg_map = {r'[1-2][1-2]' : 'Ativo Especial',
r'[1-2][3-4]' : 'Ativo Normal',
r'[3][1-2]' : 'Em Atenção',
r'[3][3-4]' : 'Em Alerta',
r'[4][1-2]' : 'Recuperar Importantes',
r'[4][3-4]' : 'Recuperar Médios'}
# Rotulando os grupos.
df_seg['Segment'] = df_seg['RF_Score'].replace(seg_map, regex = True)
df_seg.head(10)
ID | R | F | M | R_Score | F_Score | M_Score | RFM_Score | RF_Score | Segment | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 5524 | 58 | 25 | 27845 | 3 | 1 | 1 | 311 | 31 | Em Atenção |
1 | 2174 | 38 | 6 | 520 | 2 | 4 | 4 | 244 | 24 | Ativo Normal |
2 | 4141 | 26 | 21 | 12865 | 2 | 2 | 2 | 222 | 22 | Ativo Especial |
3 | 6182 | 26 | 8 | 965 | 2 | 4 | 4 | 244 | 24 | Ativo Normal |
4 | 5324 | 94 | 19 | 6810 | 4 | 2 | 3 | 423 | 42 | Recuperar Importantes |
5 | 7446 | 16 | 22 | 10810 | 1 | 1 | 2 | 112 | 11 | Ativo Especial |
6 | 965 | 34 | 21 | 9430 | 2 | 2 | 2 | 222 | 22 | Ativo Especial |
7 | 6177 | 32 | 10 | 3070 | 2 | 3 | 3 | 233 | 23 | Ativo Normal |
8 | 4855 | 19 | 6 | 840 | 1 | 4 | 4 | 144 | 14 | Ativo Normal |
9 | 5899 | 68 | 2 | 960 | 3 | 4 | 4 | 344 | 34 | Em Alerta |
# Contagem dos grupos.
df_seg['Segment'].value_counts()
Ativo Normal 570 Ativo Especial 536 Em Atenção 304 Recuperar Médios 290 Em Alerta 258 Recuperar Importantes 247 Name: Segment, dtype: int64
# Segmentação de clientes.
p1 = df_seg['Segment'].value_counts().plot.pie(startangle = 180,
explode = (0, 0, 0, 0, 0, 0.1),
autopct = '%.1f',
shadow = True,
ylabel = '',
colors = ['lightblue',
'royalblue',
'orange',
'tomato',
'wheat',
'crimson'])
plt.axis('equal')
plt.title('Segmentação dos Clientes\n', size = 20);
Após a análise RFM os clientes foram segmentados em 6 grupos, e agora, podemos criar algumas sugestões de campanhas para cada um deles.
Esses são clientes recentes, que compraram com muita frequência.
Para esse grupo, podemos criar campanhas visando recompensar e fidelizar esses clientes, como por exemplo:
Esse grupo, assim como o anterior, também possui clientes que compraram recentemente, porém, com menor frequência.
Aqui, poderíamos adotar algumas estratégias visando aumentar a frequência de compra dos clientes, como por exemplo:
Esse grupo merece atenção pois possui clientes que compraram com muita frequência e que estão "hibernados" a algum tempo.
Isso pode ser um sinal de que a BestShop está perdendo bons clientes para a concorrência e devemos evitar que isso ocorra.
Poderíamos criar algumas estratégias como:
Esse pode ser considerado um grupo de clientes que compram eventualmente.
Aqui, poderíamos adotar algumas estratégias semelhantes ao grupo anterior visando reativar o interesse desses clientes.
Nesse grupo, temos clientes que já compraram com muita frequência e que atualmente estão inativos.
Devemos adotar estratégias visando recuperar esses clientes, como por exemplo:
Esse grupo se trata de clientes que compraram com menos frequência e que estão a muito tempo sem comprar.
Semelhante ao ocorrido com o grupo anterior, devemos focar em reviver o interesse do cliente.
Neste projeto, utilizamos a linguagem Python e algumas de suas principais bibliotecas para segmentar a base de clientes da BestShop.
A segmentação foi realizada através da análise RFM, que é uma ótima maneira de identificar quais são os clientes mais e menos engajados da empresa. Além disso, também realizamos uma breve análise exploratória nos dados, respondendo perguntas e hipóteses de negócio, com o objetivo de entender e extrair insights dos dados.
O resultado da segmentação dos clientes gerou seis grupos, onde cada um deles possui um padrão de consumo específico, o que possibilita a empresa direcionar ações customizadas para cada grupo, visando aumentar as taxas de conversão das campanhas de Marketing.