Segmentação de Clientes - RFM Analysis¶

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.

1. Entendimento do Negócio¶

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.

1.1 Dicionário de Dados¶

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

1.2 Estratégia da Solução¶

Como estratégia para a solução do projeto, definimos as seguintes etapas:

  • 1. Entendimento do Negócio: nesta etapa inicial, o principal objetivo é compreender o problema de negócio e as necessidades do cliente.
  • 2. Entendimento dos Dados: iremos tratar as principais inconsistências encontradas nos dados, e realizar uma análise exploratória a fim de gerar insights sobre o negócio.
  • 3. Engenharia de Atributos: com os dados existentes, criaremos variáveis a fim de extrair novas informações dos dados.
  • 4. Perguntas e Hipóteses de Negócio: ainda como forma de explorar os dados, criaremos perguntas e hipóteses relacionadas ao problema de negócio.
  • 5. Análise RFM: através da análise RFM, iremos segmentar os clientes da empresa.
  • 6. Recomendações para Área de Marketing: nessa etapa, iremos sugerir algumas recomendações para a área de Marketing.
  • 7. Conclusões Finais: por fim, concluiremos o projeto com as considerações finais.

2. Entendimento dos Dados¶

Vamos iniciar o projeto carregando as bibliotecas necessárias e os conjuntos de dados.

2.1 Bibliotecas Necessárias¶

In [1]:
# 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
In [2]:
# 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
In [3]:
# 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

In [4]:
# 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)

2.2 Tratamento Inicial dos Dados¶

Nessa etapa, nosso objetivo é realizar uma análise geral nos dados a fim de tratar possíveis inconsistências.

In [5]:
# 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.

In [6]:
# Cópia do dataset.
df1 = df
In [7]:
# Visualizando o conjunto de dados.
df1.head()
Out[7]:
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
In [8]:
# 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
In [9]:
# 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
In [10]:
# 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
Out[10]:
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.

In [11]:
# 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)
In [12]:
# 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.

In [13]:
# Excluindo as variáveis.
df1 = df1.drop(['Z_CostContact', 'Z_Revenue'], axis = 1)
In [14]:
# 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.

2.3 Análise Exploratória¶

Baseado no dicionário de dados, vamos separar as variáveis.

In [15]:
# Identificação dos clientes.
id_clientes = ['ID']
In [16]:
# Variável periódica.
data = ['Dt_Customer']

# Convertendo o tipo da variável.
df2['Dt_Customer'] = pd.to_datetime(df2['Dt_Customer'])
In [17]:
# Variáveis numéricas.
nums = ['Year_Birth',
        'Income',
        'Kidhome',
        'Teenhome',
        'Recency',
        'MntWines',
        'MntFruits',
        'MntMeatProducts',
        'MntFishProducts',
        'MntSweetProducts',
        'MntGoldProds',
        'NumDealsPurchases',
        'NumWebPurchases',
        'NumCatalogPurchases',
        'NumStorePurchases',
        'NumWebVisitsMonth']
In [18]:
# Variáveis categóricas.
cats = ['Education',
        'Marital_Status',
        'AcceptedCmp1',
        'AcceptedCmp2',
        'AcceptedCmp3',
        'AcceptedCmp4',
        'AcceptedCmp5',
        'Complain',
        'Response']
In [19]:
# Total de variáveis nas listas.
len(id_clientes + data + nums + cats)
Out[19]:
27

2.3.1 Variável Periódica¶

In [20]:
# 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.

2.3.2 Variáveis Numéricas¶

In [21]:
# Estatísticas das variáveis.
df2[nums].describe().T
Out[21]:
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
In [22]:
# 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()
In [23]:
# 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()

2.3.3 Tratando Valores Outliers¶

Iremos tratar as seguintes variáveis:

  • Year_Birth possui registros muito antigos.
    • A idade máxima considerada será de 100 anos.
  • Income possui valores outliers, ou seja, valores muito distantes da média.
    • O limite de corte será a média da variável + 3 x o valor do desvio padrão.
In [24]:
# 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
In [25]:
# 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
In [26]:
# Tratando os valores outliers.
df2 = df2[(df2['Year_Birth'] >= 1922) & (df2['Income'] <= limite)]
In [27]:
# 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
In [28]:
# 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);

2.3.4 Variáveis Categóricas¶

In [29]:
# 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()
In [30]:
# 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()
In [31]:
# Cópia do dataframe.
df3 = df2.copy()

3. Engenharia de Atributos¶

As seguintes variáveis serão criadas de acordo com as regras abaixo:

  • Age informa a idade do cliente.
    • 2015 (ano seguinte ao último registro do dataset) - ano de nascimento.
  • Group_Age informa a que grupo o cliente pertence conforme sua idade.
    • 1 = clientes de 18 a 25 anos;
    • 2 = clientes de 25 a 40 anos;
    • 3 = clientes de 40 a 60 anos;
    • 4 = clientes de 60 a 100 anos.
  • 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.
    • Receberá a data (mês e ano) seguinte ao último registro do dataset.
  • CustomerMonth informa o tempo de vida do cliente (em meses).
  • TotalProd total de produtos vendidos.
    • Receberá o valor da soma das 6 categorias de produtos.
  • TotalPurchases total de vendas realizadas.
    • Receberá o valor da soma das 4 categorias dos locais de compras.
  • TotalPromo informa a quantidade de campanhas de marketing que o cliente aceitou.
    • Receberá o valor da soma das variáveis referentes as campanhas de marketing.
In [32]:
# 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
In [33]:
# 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'))
In [34]:
# Variável "Graduate".
df3.insert(5, 'Graduate', df3['Education'].apply(lambda x: 'Básico' if x == 'Basic' else 'Superior'))
In [35]:
# 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')
In [36]:
# Variável "Kids".
df3.insert(11, 'Kids', df3['Kidhome'] + df3['Teenhome'])
In [37]:
# Variável "CurrentDay".
df3.insert(13, 'CurrentDay', datetime.strptime('2014-07', '%Y-%m'))
In [38]:
# Variável "CustomerMonth".
df3.insert(14, 'CustomerMonth', ((df3['CurrentDay'] - df3['Dt_Customer']) / 30).apply(lambda x: x.days).astype('int64'))
In [39]:
# Variável "TotalProd".
df3.insert(22, 'TotalProd', 
           df3['MntWines'] + 
           df3['MntFruits'] + 
           df3['MntMeatProducts'] + 
           df3['MntFishProducts'] +
           df3['MntSweetProducts'] + 
           df3['MntGoldProds'])
In [40]:
# Variável "TotalPurchases".
df3.insert(27, 'TotalPurchases', 
           df3['NumDealsPurchases'] + 
           df3['NumWebPurchases'] + 
           df3['NumCatalogPurchases'] + 
           df3['NumStorePurchases'])
In [41]:
# Variável "TotalPromo".
df3.insert(36, 'TotalPromo', 
           df3['AcceptedCmp1'] + 
           df3['AcceptedCmp2'] + 
           df3['AcceptedCmp3'] + 
           df3['AcceptedCmp4'] + 
           df3['AcceptedCmp5'] + 
           df3['Response'])
In [42]:
# Cópia do dataframe.
df4 = df3.copy()
In [43]:
# 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)
In [44]:
# Visualizando o dataframe.
df4.head()
Out[44]:
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

4. Perguntas e Hipóteses de Negócio¶

Agora que entendemos o que as variáveis representam, vamos criar algumas perguntas e hipóteses de negócio.

  • P1. Qual o produto mais vendido da empresa?
  • P2. Existe correlação significativa entre os produtos?
  • P3. Qual campanha de marketing teve maior aceitação entre os clientes?
  • P4. Qual o local de compras responsável pelo maior número de vendas?
  • H1. Em média, clientes mais jovens compram mais pela internet.
  • H2. Clientes com ensino superior comprar mais.
  • H3. Em média, clientes solteiros compram mais que clientes casados.
  • H4. Clientes "alta renda" compram mais.
  • H5. Clientes que possuem mais filhos compram mais.
  • H6. Clientes antigos compram mais que clientes novos.

P1. Qual o produto mais vendido da empresa?¶

In [45]:
# 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);
  • Vinho é o produto mais vendido da empresa.

P2. Existe correlação significativa entre os produtos?¶

In [46]:
# 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);
  • Com exceção do vinho e do ouro, os demais produtos possuem uma correlação de aproximadamente 0.60.
  • A carne é o único produto correlacionado positivamente com vinho.

P3. Qual campanha de marketing teve maior aceitação entre os clientes?¶

In [47]:
# 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);
  • Considerando a variável Response sendo a 6º campanha realizada, essa foi a de maior aceitação entre os clientes.

P4. Qual o local de compras responsável pelo maior número de vendas?¶

In [48]:
# 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);
  • O maior número de vendas da empresa acontece em lojas físicas.

H1. Em média, clientes mais jovens compram mais pela internet.¶

In [49]:
# 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);
  • Falsa, podemos observar que os grupos 3 e 4 possuem as maiores médias de compra pela internet.

H2. Clientes com ensino superior compram mais.¶

In [50]:
# 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);
  • Verdadeira, tanto no número total quanto na média, clientes com ensino superior compram mais.

H3. Em média, clientes solteiros compram mais que clientes casados.¶

In [51]:
# 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);
  • Verdadeira, a média de compras de clientes solteiros é ligeiramente maior.

H4. Clientes "alta renda" compram mais.¶

In [52]:
# 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);
  • Verdadeira, há uma alta correlação positiva entre a renda do cliente e o total de produtos comprados.

H5. Clientes que possuem mais filhos compram mais.¶

In [53]:
# 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);
  • Falsa, quantos mais filhos os clientes possuem menos eles compram.

H6. Clientes antigos compram mais que clientes novos.¶

In [54]:
# 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);
  • Falsa, não há nenhuma relação entre o tempo de vida do cliente com o total de compras realizadas.

5. Análise RFM¶

A análise RFM é uma estratégia para estimar o valor de um cliente, com base em três variáveis:

  • Recency (recência): quanto mais recente tiver sido a última compra de um cliente, mais pontos ele recebe aqui.
  • Frequency (frequência): quanto maior for o número de compras de um cliente, maior também será sua pontuação neste quesito.
  • Monetary (monetário): quanto maior for o gasto do cliente em compras, maior a pontuação.

5.1 Definindo os Preços dos Produtos¶

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:

  • Vinhos: são vendidos a 15 reais.
  • Frutas: são vendidas a 5 reais.
  • Carnes: são vendidas a 20 reais.
  • Peixes: são vendidos a 20 reais.
  • Doces: são vendidos a 10 reais.
  • Produtos Ouro: são vendidos a 30 reais.
In [55]:
# Definindo os preços dos produtos.
vinho = 15
fruta = 5
carne = 20
peixe = 20
doce = 10
ouro = 30 
In [56]:
# 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))
In [57]:
# Visualizando as variáveis
df4[['MntWines',
    'MntFruits',
    'MntMeatProducts',
    'MntFishProducts',
    'MntSweetProducts',
    'MntGoldProds',
    'Total$']].head()
Out[57]:
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
In [58]:
# Dataframe para a análise RFM.
df_seg = df4[['ID', 
              'Recency', 
              'TotalPurchases', 
              'Total$']]
In [59]:
# Renomeando as colunas.
df_seg.rename(columns = {'Recency': 'R', 
                         'TotalPurchases': 'F', 
                         'Total$': 'M'}, inplace = True)
In [60]:
# Visualizando o dataframe.
df_seg.head()
Out[60]:
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.

In [61]:
# 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);
  • A recência dos clientes está bem distribuída, entre 0 e 100 dias.
  • Poucos clientes estão a mais de 30 dias sem realizarem pelo menos uma compra.
  • A maioria dos clientes gastam até 10.000,00 reais.

5.2 Segmentando Clientes¶

Com os valores RFM definidos podemos iniciar a segmentação. Vamos dividir os dados em quatro quartis.

In [62]:
# Divisão do dataframe.
quantiles = df_seg.quantile(q = [0.25, 0.5, 0.75, 1]) 
quantiles
Out[62]:
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.

In [63]:
# 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.

In [64]:
# 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.

In [65]:
# 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
In [66]:
# 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))
In [67]:
# Visualizando o dataframe.
df_seg.head()
Out[67]:
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.

In [68]:
# 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()
Out[68]:
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
In [69]:
# 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']
Out[69]:
46

5.3 Rotulando os Grupos de Clientes¶

Visando diminuir o número de grupos criados na etapa anterior, utilizaremos apenas os scores de recência e frequência.

In [70]:
# Variável "RF_Score".
df_seg['RF_Score'] = df_seg['R_Score'].astype(str) + df_seg['F_Score'].astype(str)
In [71]:
# Visualizando o dataframe.
df_seg.head()
Out[71]:
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:

  • Ativo Especial: clientes recentes que compram com muita frequência;
  • Ativo Normal: clientes recentes que compram com menos frequência;
  • Em Atenção: clientes que compram com muita frequência e que estão a algum tempo sem comprar;
  • Em Alerta: clientes que compram com menos frequência e que estão a algum tempo sem comprar;
  • Recuperar Importantes: clientes que compram com muita frequência e que estão a muito tempo sem comprar;
  • Recuperar Médios: clientes que compram com menos frequência e que estão a muito tempo sem comprar.
In [72]:
# 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'}
In [73]:
# Rotulando os grupos.
df_seg['Segment'] = df_seg['RF_Score'].replace(seg_map, regex = True)
df_seg.head(10)
Out[73]:
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
In [74]:
# Contagem dos grupos. 
df_seg['Segment'].value_counts()
Out[74]:
Ativo Normal             570
Ativo Especial           536
Em Atenção               304
Recuperar Médios         290
Em Alerta                258
Recuperar Importantes    247
Name: Segment, dtype: int64
In [75]:
# 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);

6. Recomendações para Área de Marketing¶

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.

6.1 Ativo Especial¶

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:

  • Ter acesso a novos produtos antes do lançamento;
  • Oferecer descontos exclusivos.

6.2 Ativo Normal¶

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:

  • Criar promoções estimulando a recompra de outros produtos;
  • Criar programas de fidelidade.

6.3 Em Atenção¶

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:

  • Oferecer descontos baseados em produtos comprados anteriormente;
  • Criar promoções "cobrindo" as ofertas da concorrência.

6.4 Em Alerta¶

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.

6.5 Recuperar Importantes¶

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:

  • Apresentar e oferecer novos produtos baseado no histórico de compras;
  • Investir em pesquisas de feedback para descobrir as principais causas do churn.

6.6 Recuperar Médios¶

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.

  • Oferecer produtos e descontos especiais;
  • Criar campanhas de alcance customizadas.

7. Conclusões Finais¶

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.