A Hotmart é uma empresa de tecnologia brasileira que possui uma plataforma online de comercialização de conhecimento. A plataforma permite que as pessoas comercializem conteúdo de diversas áreas do conhecimento ou revendem o conteúdo produzido por terceiros. Este notebook apresenta um estudo detalhado das vendas dos produtos da Hotmart no ano de 2016 e tem como objetivo explorar algumas técnicas de análise preponderantes para no ofício da ciência de dados.
A base de dados a ser analisada possui mais de 1,5 milhões de registros e aproximadamente 227 MB de espaço em disco, portanto não se fez necessário a utilização de um serviço distribuído de arquivos em clusters de máquinas para o processamento e abertura dos dados. Ademais, o fato da base possuir apenas 227 MB de espaço permite que ela seja facilmente carregada na memória de laptop comum.
Inicialmente, apresentar-se-á brevemente os dados do data set e em seguida será realizada uma transformação no valor de cada venda para que seja possível analisar o faturamento gerado pela empresa; uma vez que essa informação está codificada em termos do zscore. Posteriormente, a quarta seção deste estudo se dedicará a apresentar uma detalhadada exploração dos dados segmentada por produtos e produtores de conteúdo. Por fim, na quinta e última seção se aplicará um modelo de machine learning na série temporal das vendas a fim de se estimar o volume futuro das mesmas. Enjoy!!!
import pandas as pd
# Carregamento do data set
sales = pd.read_csv('https://query.data.world/s/enrwqqtwlxc55skwsx735hp2yldis4', sep=';',
encoding='utf-8',header=0)
# Visualização dos dados
sales.sample(5)
purchase_id | product_id | affiliate_id | producer_id | buyer_id | purchase_date | product_creation_date | product_category | product_niche | purchase_value | affiliate_commission_percentual | purchase_device | purchase_origin | is_origin_page_social_network | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
516326 | 11896030 | 150610.0 | 4462059.0 | 4462059.0 | 1584520.0 | 2016-03-06 08:05:58 | 2015-05-09 13:33:03 | Phisical book | Child psychology | 0.549 | 0.0 | eReaders | Origin f943 | 0,0 |
88661 | 11017693 | 149675.0 | 3599751.0 | 3599751.0 | 5996711.0 | 2016-01-12 13:10:42 | 2015-05-03 17:44:43 | Phisical book | Anxiety management | 0.228 | 0.0 | eReaders | Origin d834 | 1,0 |
6601 | 10852822 | 89321.0 | 2510048.0 | 2510048.0 | 1158837.0 | 2016-01-02 10:50:33 | 2014-03-03 19:22:19 | Phisical book | Presentation skills | -0.448 | 0.0 | Smart TV | Origin ef2b | 0,0 |
918259 | 12702895 | 132809.0 | 464846.0 | 464846.0 | 6590744.0 | 2016-04-20 12:26:01 | 2015-01-16 09:37:21 | Phisical book | Personal finance | -0.453 | 0.0 | Smart TV | Origin ef2b | 0,0 |
887046 | 12641630 | 184522.0 | 2755272.0 | 2755272.0 | 339681.0 | 2016-04-17 07:48:00 | 2015-11-08 20:26:28 | Phisical book | Personal finance | -0.468 | 0.0 | Desktop | Origin d9a6 | 0,0 |
# Exclusão dos registros que possuem valores nulos
sales = sales.dropna()
len(sales)
1599828
Sejam m, s, l, u e x a média, o desvio padrão, o valor mínimo, o valor máximo e um valor qualquer do conjunto X respectivamente. Tem-se:
Onde z representa o zscore de x. Sejam y e y', o valor x e do zscore de x normalizados respectivamente:
$$ y = \frac{x - l}{u - l} \space (2); $$Substituindo a equação (1) em (3), obtém-se:
$$y' = \frac{z - min(Z)}{max(Z) - min(Z)} = \frac{\frac{x-m}{s} - \frac{l-m}{s}}{\frac{u-m}{s} - \frac{l-m}{s}} = \frac{ \frac{x - \not{m} - (l- \not{m})}{\not{s}} }{ \frac{u- \not{m}-(l- \not{m})}{\not{s}}} = \frac{x - l}{u - l} = y$$
O resultado anterior mostra que normalizar o conjunto de zscores de uma variável é equivalente à se normalizar os valores da própria variável. Assim, é possível captar percentualmente o faturamento que cada produto, produtor, categoria ou qualquer outro segmento gerou para a Hotmart sem a necessidade de se utilizar os valores reais das vendas.
from sklearn import preprocessing
# Transformação Min-Max na coluna purchase_value
min_max_scaler = preprocessing.MinMaxScaler()
sales['purchase_value_norm'] = min_max_scaler.fit_transform(sales[['purchase_value']].values)
sales[['product_id','purchase_value','purchase_value_norm']].sample(6)
product_id | purchase_value | purchase_value_norm | |
---|---|---|---|
1569651 | 80755.0 | -0.466 | 0.000600 |
349851 | 112160.0 | 0.358 | 0.007186 |
1122773 | 163925.0 | 0.310 | 0.006802 |
106535 | 164264.0 | 2.218 | 0.022054 |
80578 | 3336.0 | -0.397 | 0.001151 |
810574 | 171112.0 | 2.269 | 0.022462 |
Nesta etapa do estudo, analisar-se-á o total de vendas realizadas e o faturamento gerado por cada produto e produtor de conteúdo.
Apresenta-se nesta seção a análise das vendas e do faturamento da Hotmart segmentada por produto.
# Define um método sales_by_id responsável por segmentar as vendas por um dado ID
def sales_by_id(sales,column_id,column_name):
df = sales.groupby(column_id).count()[['purchase_id']].sort_values(by=['purchase_id'],
ascending=False)
df.columns = [column_name]
# Rankeamento dos itens por quantidade de vendas
df['sales_rank'] = df[column_name].rank(ascending=False)
# Percentual do total de vendas por item
df['%_sales'] = sales[column_id].value_counts(normalize=True) *100
return df
# Obtém as vendas segmentadas por produtos
by_products = sales_by_id(sales,'product_id','sales_by_product')
# ------------------------------------------ Cálculo do faturamento gerado por cada produto
# Lista de produtos
unique = sales.drop_duplicates(subset=['product_id'])
by_products = pd.merge(by_products,
unique[['product_id','purchase_value_norm','product_niche','product_category',
'purchase_device']],
how='inner',on='product_id')
# Cálculo da receita de cada produto
by_products['revenue_by_product'] = by_products['sales_by_product'] * by_products['purchase_value_norm']
del by_products['purchase_value_norm']
# Rankeamento dos produtos por faturamento
by_products['revenue_rank'] = by_products['revenue_by_product'].rank(ascending=False)
# Percentual do faturamento por produtos
by_products['%_revenue'] = 100*(by_products['revenue_by_product']/by_products['revenue_by_product'].sum())
# Ordenação das colunas
by_products = by_products[['product_id','sales_rank','sales_by_product','%_sales',
'revenue_rank','revenue_by_product','%_revenue',
'product_niche','product_category','purchase_device']]
# Resultado
by_products.head(10)
product_id | sales_rank | sales_by_product | %_sales | revenue_rank | revenue_by_product | %_revenue | product_niche | product_category | purchase_device | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 219755.0 | 1.0 | 41220 | 2.576527 | 1382.0 | 0.658982 | 0.010883 | Immigration | Phisical book | Desktop |
1 | 130294.0 | 2.0 | 32731 | 2.045907 | 1.0 | 92.618615 | 1.529521 | Immigration | Phisical book | eReaders |
2 | 42903.0 | 3.0 | 27228 | 1.701933 | 4.0 | 62.899810 | 1.038739 | YouTube video creation | Phisical book | Smart TV |
3 | 63718.0 | 4.0 | 24132 | 1.508412 | 11.0 | 45.909866 | 0.758164 | YouTube video creation | Phisical book | eReaders |
4 | 132809.0 | 5.0 | 23350 | 1.459532 | 2017.0 | 0.373295 | 0.006165 | Personal finance | Phisical book | Smart TV |
5 | 83377.0 | 6.0 | 21601 | 1.350208 | 3.0 | 64.404830 | 1.063594 | Anxiety management | Phisical book | Smart TV |
6 | 149048.0 | 7.0 | 16386 | 1.024235 | 457.0 | 2.357660 | 0.038935 | Negotiation | Phisical book | Smart TV |
7 | 59205.0 | 8.0 | 16096 | 1.006108 | 9.0 | 47.605314 | 0.786163 | Negotiation | Phisical book | eReaders |
8 | 154310.0 | 9.0 | 14455 | 0.903535 | 144.0 | 7.510471 | 0.124029 | Government | Podcast | Smart TV |
9 | 132454.0 | 10.0 | 11685 | 0.730391 | 333.0 | 3.175729 | 0.052445 | Online course creation | Phisical book | eReaders |
# Total de produtos
len(by_products)
17883
# Ordena os produtos por faturamento
by_products = by_products.sort_values(by=['%_revenue'],ascending=False)
# Reordenação das colunas
by_products[['product_id','revenue_rank','revenue_by_product','%_revenue',
'sales_rank','sales_by_product','%_sales',
'product_niche','product_category','purchase_device']].head(10)
product_id | revenue_rank | revenue_by_product | %_revenue | sales_rank | sales_by_product | %_sales | product_niche | product_category | purchase_device | |
---|---|---|---|---|---|---|---|---|---|---|
1 | 130294.0 | 1.0 | 92.618615 | 1.529521 | 2.0 | 32731 | 2.045907 | Immigration | Phisical book | eReaders |
201 | 206775.0 | 2.0 | 75.616825 | 1.248751 | 202.0 | 1226 | 0.076633 | Careers | Phisical book | eReaders |
5 | 83377.0 | 3.0 | 64.404830 | 1.063594 | 6.0 | 21601 | 1.350208 | Anxiety management | Phisical book | Smart TV |
2 | 42903.0 | 4.0 | 62.899810 | 1.038739 | 3.0 | 27228 | 1.701933 | YouTube video creation | Phisical book | Smart TV |
12 | 138480.0 | 5.0 | 58.973757 | 0.973904 | 13.0 | 9903 | 0.619004 | Presentation skills | Phisical book | Desktop |
11 | 191898.0 | 6.0 | 53.892935 | 0.889998 | 12.0 | 10018 | 0.626192 | Anxiety management | Phisical book | eReaders |
89 | 202509.0 | 7.0 | 50.045243 | 0.826457 | 90.0 | 2376 | 0.148516 | Accounting | Phisical book | eReaders |
137 | 209799.0 | 8.0 | 48.775911 | 0.805495 | 138.0 | 1622 | 0.101386 | Negotiation | Phisical book | eReaders |
7 | 59205.0 | 9.0 | 47.605314 | 0.786163 | 8.0 | 16096 | 1.006108 | Negotiation | Phisical book | eReaders |
20 | 150610.0 | 10.0 | 46.634338 | 0.770128 | 21.0 | 6253 | 0.390855 | Child psychology | Phisical book | eReaders |
# Define um método sales_relevance responsável por computar a revelância de conjuntos de ranking dos produtos
def sales_relevance(df,rankings,text):
# Percentual cumulativo do total de vendas de cada produto do ranking
rank_cum_sales = pd.DataFrame({'%':df['%_sales'].cumsum()})
# Percentual cumulativo do faturamento de cada produto do ranking
rank_cum_revenue = pd.DataFrame({'%':df['%_revenue'].cumsum()})
# Output
percentage_sales = []
percentage_revenue = []
ranks = []
percentage_amount = []
for value in rankings:
# Computa a quantidade de produtos dentro dentro de determinado percentual cumlativo
ranks.append(text.format(str(value)))
percentage_sales.append(round(rank_cum_sales .iloc[value-1,0],2))
percentage_revenue.append(round(rank_cum_revenue.iloc[value-1,0],2))
# Computa a quantidade percentual de itens
percentage_amount.append(round(100*(value/len(df)),2))
return pd.DataFrame({'Rankings':ranks,'Percentage Amount (%)':percentage_amount,
'Relevance - Sales (%)':percentage_sales,
'Relevance - Revenue (%)':percentage_revenue})
# Rankeamento dos produtos por faturamento
by_products = by_products.sort_values(by=['%_sales'],ascending=False)
# Rankings a serem analasidados
rankings = [1,5,10,20,60,120,240,450,800,1500,3000,5000]
text = "TOP {} produtos mais vendidos"
# Relevância dos rankings
sales_relevance(by_products,rankings,text)
Rankings | Percentage Amount (%) | Relevance - Sales (%) | Relevance - Revenue (%) | |
---|---|---|---|---|
0 | TOP 1 produtos mais vendidos | 0.01 | 2.58 | 0.01 |
1 | TOP 5 produtos mais vendidos | 0.03 | 9.29 | 3.34 |
2 | TOP 10 produtos mais vendidos | 0.06 | 14.31 | 5.41 |
3 | TOP 20 produtos mais vendidos | 0.11 | 19.51 | 9.49 |
4 | TOP 60 produtos mais vendidos | 0.34 | 30.49 | 15.77 |
5 | TOP 120 produtos mais vendidos | 0.67 | 39.87 | 24.28 |
6 | TOP 240 produtos mais vendidos | 1.34 | 50.17 | 35.37 |
7 | TOP 450 produtos mais vendidos | 2.52 | 60.21 | 44.73 |
8 | TOP 800 produtos mais vendidos | 4.47 | 69.81 | 58.33 |
9 | TOP 1500 produtos mais vendidos | 8.39 | 80.10 | 72.68 |
10 | TOP 3000 produtos mais vendidos | 16.78 | 89.76 | 85.77 |
11 | TOP 5000 produtos mais vendidos | 27.96 | 94.94 | 93.05 |
# Define um método by_topProducts responsável por analisar os produtos de um terminado rank por um determinado
# filtro
def by_topProducts(filter, rank):
output = by_products[[filter,'product_id']].head(rank).groupby([filter]).count().\
sort_values(by=['product_id'],ascending=False)
output.columns = ['Total de produtos']
return output
by_topProducts('product_niche',450).head(15)
Total de produtos | |
---|---|
product_niche | |
Negotiation | 78 |
Anxiety management | 75 |
Personal finance | 58 |
Presentation skills | 40 |
Government | 32 |
Organization | 22 |
Careers | 18 |
Procrastination | 18 |
Online course creation | 16 |
Accounting | 14 |
Biology | 14 |
YouTube video creation | 11 |
Media training | 11 |
Immigration | 10 |
Economics | 9 |
by_topProducts('product_category',450)
Total de produtos | |
---|---|
product_category | |
Phisical book | 379 |
Podcast | 57 |
Workshop | 11 |
eBook | 2 |
Subscription | 1 |
by_topProducts('purchase_device',450)
Total de produtos | |
---|---|
purchase_device | |
eReaders | 199 |
Desktop | 139 |
Smart TV | 105 |
Cellphone | 6 |
Tablet | 1 |
A partir dos resultados mostrados nas três tabelas acima é possível concluir que os nichos de negociação, controle de ansiedade, finanças pessoais e habilidades de apresentação foram os que mais ocorrem dentre os 450 produtos mais vendidos. Além disso, pode-se afirmar que os consumidores consumiram mais livros físicos através de eReaders, Desktop e SmartTV. Essas informações podem ser extremamente úteis para as estratégias futuras de marketing da empresa, pois revelam de certa forma as características que mais impactam no sucesso de um produto.
# Ordenação dos dados por faturamento
by_products = by_products.sort_values(by=['%_revenue'],ascending=False)
rankings = [1,5,10,20,60,120,240,450,800,1500,3000,5000]
text = "TOP {} produtos que mais faturam"
# Relevância dos rankings
sales_relevance(by_products,rankings,text)
Rankings | Percentage Amount (%) | Relevance - Sales (%) | Relevance - Revenue (%) | |
---|---|---|---|---|
0 | TOP 1 produtos que mais faturam | 0.01 | 2.05 | 1.53 |
1 | TOP 5 produtos que mais faturam | 0.03 | 5.79 | 5.85 |
2 | TOP 10 produtos que mais faturam | 0.06 | 8.07 | 9.93 |
3 | TOP 20 produtos que mais faturam | 0.11 | 11.39 | 15.97 |
4 | TOP 60 produtos que mais faturam | 0.34 | 15.86 | 30.37 |
5 | TOP 120 produtos que mais faturam | 0.67 | 21.04 | 41.56 |
6 | TOP 240 produtos que mais faturam | 1.34 | 30.21 | 54.00 |
7 | TOP 450 produtos que mais faturam | 2.52 | 40.78 | 65.22 |
8 | TOP 800 produtos que mais faturam | 4.47 | 51.01 | 75.36 |
9 | TOP 1500 produtos que mais faturam | 8.39 | 65.39 | 85.38 |
10 | TOP 3000 produtos que mais faturam | 16.78 | 79.60 | 93.73 |
11 | TOP 5000 produtos que mais faturam | 27.96 | 87.62 | 97.52 |
Os resultados mostrados na tabela acima indicam, dentre outras coisas, que os 60 produtos que mais faturam (cerca de 0,67% dos produtos disponíveis) foram responsáveis por mais de 30% do faturamento total da Hotmart em 2016.
A imagem a seguir apresenta o faturamento de alguns subconjuntos do ranking de faturamento.
import matplotlib.pyplot as plt # Matlab-style plotting
from matplotlib import pyplot
from matplotlib import colors as mcolors
import numpy as np
# Define um método scaterr_plot reponsável por gerar um gráfico de pontos identificados por labels
def scaterr_plot(rankings,colors,df,item,title,xlim,ylim):
labels = []
data = []
i=0
for rank in rankings:
revenue_percentage = round(df.iloc[i:rank,6].sum(),2)
labels.append("{}% do faturamento | {} {} | Top {} em receita".format(str(revenue_percentage),
len(df.iloc[i:rank,6]),
item,rank))
data.append((np.log1p(df.iloc[i:rank,2]),
np.log1p(df.iloc[i:rank,5])))
i=rank
fig = plt.figure(figsize=(12, 9))
ax = fig.add_subplot(1, 1, 1, facecolor="10")
for data,color,label in zip(data,colors,labels):
x,y=data
ax.scatter(x,y, alpha=0.8, c=color, edgecolors='none', s=30, label=label)
plt.title(title, fontsize=16)
plt.xlabel('Log(Qtde de vendas)', fontsize=14)
plt.ylabel('Log(Receita gerada)', fontsize=14)
plt.xlim(xlim)
plt.ylim(ylim)
plt.legend(loc='upper left')
plt.grid(True)
plt.show()
# Reordenação das colunas
by_products = by_products.sort_values(by=['%_revenue'],ascending=False)
rankings = [5,10,60,240,800,3000,5000,len(by_products)]
colors = ('red','green','brown','blue','black','purple','olive','gold')
item = 'produtos'
title = 'Distribuição dos produtos por quantidade de vendas versus faturamento'
xlim = (0.5,12)
ylim = (0.000001,5)
# Gerea o gráfico
scaterr_plot(rankings,colors,by_products,item,title,xlim,ylim)
O gráfico mostrado acima permite extrair alguns insights interessantes. Eles são:
Nesta seção será realizada uma análise similar à realizada no item 4.1, mas segmentada por produtor de conteúdo.
by_producers = sales_by_id(sales,'producer_id','sales_by_producer')
revenue_by_producers = sales[['producer_id','purchase_value_norm']].groupby(['producer_id']).sum().\
sort_values(by=['purchase_value_norm'],ascending=False)
revenue_by_producers.columns = ['revenue_by_producer']
revenue_by_producers['revenue_rank'] = revenue_by_producers['revenue_by_producer'].rank(ascending=False)
revenue_by_producers['%_revenue'] = 100*(revenue_by_producers['revenue_by_producer']/ \
revenue_by_producers['revenue_by_producer'].sum())
by_producers['producer_id_x'] = by_producers.index
by_producers = pd.merge(by_producers,revenue_by_producers,how='inner',on='producer_id')
by_producers = by_producers[['producer_id_x','sales_rank','sales_by_producer','%_sales',
'revenue_rank','revenue_by_producer','%_revenue']]
by_producers[['sales_rank','sales_by_producer','%_sales',
'revenue_rank','revenue_by_producer','%_revenue']].head(10)
sales_rank | sales_by_producer | %_sales | revenue_rank | revenue_by_producer | %_revenue | |
---|---|---|---|---|---|---|
producer_id | ||||||
6697083.0 | 1.0 | 41220 | 2.576527 | 25.0 | 41.994756 | 0.606977 |
3992235.0 | 2.0 | 39331 | 2.458452 | 11.0 | 61.712906 | 0.891977 |
464846.0 | 3.0 | 35470 | 2.217113 | 16.0 | 53.970816 | 0.780075 |
349701.0 | 4.0 | 34568 | 2.160732 | 2.0 | 124.155321 | 1.794497 |
166090.0 | 5.0 | 28895 | 1.806132 | 18.0 | 50.821226 | 0.734552 |
442241.0 | 6.0 | 27798 | 1.737562 | 6.0 | 72.493110 | 1.047790 |
2307584.0 | 7.0 | 21720 | 1.357646 | 30.0 | 36.981807 | 0.534522 |
3382787.0 | 8.0 | 20199 | 1.262573 | 116.0 | 12.987035 | 0.187710 |
4580574.0 | 9.0 | 16386 | 1.024235 | 481.0 | 2.591062 | 0.037450 |
671256.0 | 10.0 | 16096 | 1.006108 | 20.0 | 50.407803 | 0.728577 |
# Total de produtores
len(by_producers)
8020
by_producers = by_producers.sort_values(by=['%_revenue'],ascending=False)
by_producers[['revenue_rank','revenue_by_producer','%_revenue',
'sales_rank','sales_by_producer','%_sales']].head(10)
revenue_rank | revenue_by_producer | %_revenue | sales_rank | sales_by_producer | %_sales | |
---|---|---|---|---|---|---|
producer_id | ||||||
42346.0 | 1.0 | 253.131796 | 3.658678 | 13.0 | 13240 | 0.827589 |
349701.0 | 2.0 | 124.155321 | 1.794497 | 4.0 | 34568 | 2.160732 |
3425706.0 | 3.0 | 92.860482 | 1.342173 | 77.0 | 3767 | 0.235463 |
1931767.0 | 4.0 | 88.081629 | 1.273101 | 41.0 | 5573 | 0.348350 |
2546880.0 | 5.0 | 83.825478 | 1.211584 | 15.0 | 10469 | 0.654383 |
442241.0 | 6.0 | 72.493110 | 1.047790 | 6.0 | 27798 | 1.737562 |
3672.0 | 7.0 | 67.231739 | 0.971744 | 17.0 | 10257 | 0.641131 |
1095211.0 | 8.0 | 65.729972 | 0.950038 | 56.0 | 4522 | 0.282655 |
4462059.0 | 9.0 | 63.848556 | 0.922844 | 21.0 | 8831 | 0.551997 |
1058799.0 | 10.0 | 63.826366 | 0.922524 | 122.0 | 2636 | 0.164768 |
by_producers = by_producers.sort_values(by=['%_sales'],ascending=False)
rankings = [1,5,10,30,60,100,180,300,500,1000,2000,5000]
text = "TOP {} produtores que mais vendem"
sales_relevance(by_producers,rankings,text)
Rankings | Percentage Amount (%) | Relevance - Sales (%) | Relevance - Revenue (%) | |
---|---|---|---|---|
0 | TOP 1 produtores que mais vendem | 0.01 | 2.58 | 0.61 |
1 | TOP 5 produtores que mais vendem | 0.06 | 11.22 | 4.81 |
2 | TOP 10 produtores que mais vendem | 0.12 | 17.61 | 7.34 |
3 | TOP 30 produtores que mais vendem | 0.37 | 29.59 | 17.91 |
4 | TOP 60 produtores que mais vendem | 0.75 | 39.46 | 25.86 |
5 | TOP 100 produtores que mais vendem | 1.25 | 48.55 | 36.00 |
6 | TOP 180 produtores que mais vendem | 2.24 | 60.18 | 52.39 |
7 | TOP 300 produtores que mais vendem | 3.74 | 70.38 | 65.01 |
8 | TOP 500 produtores que mais vendem | 6.23 | 79.79 | 75.79 |
9 | TOP 1000 produtores que mais vendem | 12.47 | 89.94 | 88.84 |
10 | TOP 2000 produtores que mais vendem | 24.94 | 96.34 | 96.28 |
11 | TOP 5000 produtores que mais vendem | 62.34 | 99.64 | 99.73 |
# Ordenação dos dados pelo ranking de faturamento
by_producers = by_producers.sort_values(by=['%_revenue'],ascending=False)
rankings = [1,5,10,30,60,100,180,300,500,1000,2000,5000]
text = "TOP {} produtores que mais faturam"
# Relevância dos rankings
sales_relevance(by_producers,rankings,text)
Rankings | Percentage Amount (%) | Relevance - Sales (%) | Relevance - Revenue (%) | |
---|---|---|---|---|
0 | TOP 1 produtores que mais faturam | 0.01 | 0.83 | 3.66 |
1 | TOP 5 produtores que mais faturam | 0.06 | 4.23 | 9.28 |
2 | TOP 10 produtores que mais faturam | 0.12 | 7.60 | 14.09 |
3 | TOP 30 produtores que mais faturam | 0.37 | 22.05 | 28.03 |
4 | TOP 60 produtores que mais faturam | 0.75 | 29.62 | 40.92 |
5 | TOP 100 produtores que mais faturam | 1.25 | 34.69 | 51.15 |
6 | TOP 180 produtores que mais faturam | 2.24 | 47.48 | 63.91 |
7 | TOP 300 produtores que mais faturam | 3.74 | 59.52 | 74.52 |
8 | TOP 500 produtores que mais faturam | 6.23 | 72.18 | 84.11 |
9 | TOP 1000 produtores que mais faturam | 12.47 | 84.69 | 93.33 |
10 | TOP 2000 produtores que mais faturam | 24.94 | 94.06 | 98.08 |
11 | TOP 5000 produtores que mais faturam | 62.34 | 99.32 | 99.92 |
Com base nas informações levantadas, os produtores que mais vendem não foram responsáveis pela maior parte do faturamento da Hotmart.
by_producers = by_producers.sort_values(by=['%_revenue'],ascending=False)
rankings = [5,10,50,100,500,1000,5000,len(by_producers)]
colors = ('red','green','brown','blue','black','purple','olive','gold')
item = 'produtores'
title = 'Distribuição dos produtores por quantidade de vendas versus faturamento'
xlim = (0.5,11)
ylim = (0.00000001,6)
# Gerea o gráfico
scaterr_plot(rankings,colors,by_producers,item,title,xlim,ylim)
O gráfico acima fornece informações similiares àquele apresentado na segmentação por produto. Todavia, convém ressaltar a superioridade do futaramento gerado pelo maior produtor (ponto vermelho na parte mais superior do gráfico) em relação à todos os outros produtores.
Nesta seção, objetiva-se predizer o faturamento trismestral da Hotmart no período posterior ao informado na base de dados em estudo a partir de um modelo auto regressivo integrado de médias móveis (ARIMA, do inglês). Optou-se pela utilização do modelo ARIMA devido à fácil compreensão matemática do modelo.
sales['dts'] = pd.to_datetime(sales['purchase_date'])
sales_timeSeries = sales[['dts','purchase_value_norm']].set_index('dts').\
groupby(pd.Grouper(freq='D')).sum().sort_values(by=['dts'],ascending=True)
sales_timeSeries.columns = ['revenue']
sales_timeSeries.head(10)
revenue | |
---|---|
dts | |
2015-12-31 | 0.407699 |
2016-01-01 | 11.826965 |
2016-01-02 | 24.353144 |
2016-01-03 | 23.225632 |
2016-01-04 | 25.074475 |
2016-01-05 | 23.013493 |
2016-01-06 | 16.885318 |
2016-01-07 | 21.994684 |
2016-01-08 | 38.277038 |
2016-01-09 | 28.506339 |
import warnings
warnings.filterwarnings('ignore')
fig = plt.figure(figsize=(12, 9))
ax = fig.add_subplot(1, 1, 1, facecolor="10")
ax.plot(sales_timeSeries)
plt.title('Série temporal do faturamento da Hotmart no período analisado', fontsize=16)
plt.xlabel('Tempo em dias', fontsize=14)
plt.ylabel('Faturamento em unidades do produto mais caro', fontsize=14)
Text(0, 0.5, 'Faturamento em unidades do produto mais caro')
tr_start,tr_end = '2015-12-31','2016-05-28'
te_start,te_end = '2016-05-29','2016-06-30'
tra = sales_timeSeries['revenue'][tr_start:tr_end].dropna()
tes = sales_timeSeries['revenue'][te_start:te_end].dropna()
print({'Treino': str(len(tra)) + ' amostras','Teste':str(len(tes)) + ' amostras'})
{'Treino': '150 amostras', 'Teste': '33 amostras'}
Referência: Introduction to Forecasting with ARIMA in R by Ruslana Dalinina
.Conforme foi mencionando na seção introdutória, a sigla ARIMA significa no idioma inglês autoregressive integrated moving average ou modelo auto regressivo integrado de médias móveis no português. Modelos ARIMA não sazonais são geralmente especificados pelos três parâmetros (p,d,q).
O componente auto regressivo (AR(p)) representa o grau que a série temporal Y é regressada em seus valores anteriores, ou seja, o parâmetro p especifica a quantidade de valores passados usados no modelo de regressão. Por exemplo, AR(2) ou ARIMA(2,0,0) é escrito como:
O componente integrador (I(d)) representa o grau com que os valores da série serão avaliados pelos os valores passodos. A diferenciação remove as mudanças no nível de uma série temporal, eliminando a tendência e a sazonalidade; consequentemente esse processo estabiliza a média da série temporal. Matematicamente, uma operação de diferenciação de ordem 2 ou ARIMA (0,2,0) numa série Y é escrita da seguinte forma:
O componente média móvel (MA(q)) representa o erro de regressão da série como uma combinação linear dos et termos de erros anteriores. O parâmetro q especifica o número de termos a serem incluídos no modelo. Matematicamente, a média móvel de ordem 2 ou ARIMA(0,0,2) de uma série Y é escrita da seguinte forma:
O teste de Dickey-Fuller Aumentado ADF (Augmented Dickey-Fuller) é um teste estatístico que determina se uma série temporal é estacionária ou não. Formalmente, o ADF testa a hipótese nula H0 de 1 ser a raiz da equação característica do modelo auto regressivo. Se a hipótese não for rejeitada, significa que o modelo auto regressivo possui raiz unitária e tal série é não estacionária. O termo p-value da resposta do teste ADF indica as chances do modelo possuir raiz unitária.
import statsmodels.api as sm
res = sm.tsa.adfuller(sales_timeSeries['revenue'].dropna(),regression='ct')
print('p-value:{}'.format(res[1]))
p-value:0.027783180125267357
#ADF-test(differenced-time-series)
res = sm.tsa.adfuller(sales_timeSeries['revenue'].diff().dropna(),regression='c')
print('p-value:{}'.format(res[1]))
p-value:1.1010436180807786e-12
Após a série temporal ter sido “estacionarizada” por meio da diferenciação, o próximo passo para ajustar o modelo ARIMA é determinar o grau dos termos AR(p) e MA(q) necessários para corrigir as autocorrelações que restaram na série diferenciada. Uma alternativa para a determinação desses termos é tentar algumas combinações aleatórias de e q e verificar qual tem a melhor performance. Entretanto, há uma maneira mais sofisticada de realizar tal tarefa por meio da análise dos gráficos da função de autacorrelação ACF (Autocorrelation function) e função de auto correlação parcial PAFC (Partial Autocorrelation function) da série diferenciada. Os gráficos ACF e PACF são gráficos de barras que representam o grau de associação entre o valor atual da série temporal e seus anteriores. Os gráficos mostram quais são os valores anteriores da série que são úteis para prever valores futuros. Com esse conhecimento, é possível determinar a ordem dos termos p e q do modelo ARIMA. Resumidamente, o gráfico AFC está relacionado à média móvel (MA(q)) do modelo e indica a correlação entre Yt e Yk, onde o índice k representa o atraso (lag) temporal de Yk em relação à Yt; enquanto o gráfico PAFC está relacionado à parte auto regressiva (AR(p)) do modelo ARIMA e indica a correlação entre Yt e Yk.
A seguir, exibe-se os gráficos AFC e PAFC da série temporal diferenciada.
fig,ax = plt.subplots(2,1,figsize=(20,10))
fig = sm.graphics.tsa.plot_acf(tra.diff().dropna(), lags=50, ax=ax[0])
fig = sm.graphics.tsa.plot_pacf(tra.diff().dropna(), lags=50, ax=ax[1])
plt.show()
Entretanto, utilizar-se-á o métedo arma_order_select_ic da bibloteca statsmodels.api para buscar os melhores valores de (p,q) dentro do intervalo [1,7] dos números naturais.
import warnings
warnings.filterwarnings('ignore')
resDiff = sm.tsa.arma_order_select_ic(tra, max_ar=7, max_ma=7, ic='aic', trend='c')
print('ARMA(p,q) =',resDiff['aic_min_order'],'is the best.')
ARMA(p,q) = (7, 1) is the best.
arima = sm.tsa.statespace.SARIMAX(tra,order=(7,1,1),freq='D',seasonal_order=(0,0,0,0),
enforce_stationarity=False, enforce_invertibility=False,).fit()
arima.summary()
Dep. Variable: | revenue | No. Observations: | 150 |
---|---|---|---|
Model: | SARIMAX(7, 1, 1) | Log Likelihood | -602.933 |
Date: | Tue, 13 Aug 2019 | AIC | 1223.865 |
Time: | 23:32:04 | BIC | 1250.468 |
Sample: | 12-31-2015 | HQIC | 1234.676 |
- 05-28-2016 | |||
Covariance Type: | opg |
coef | std err | z | P>|z| | [0.025 | 0.975] | |
---|---|---|---|---|---|---|
ar.L1 | -0.0711 | 0.694 | -0.102 | 0.918 | -1.431 | 1.288 |
ar.L2 | -0.4009 | 0.336 | -1.194 | 0.232 | -1.059 | 0.257 |
ar.L3 | -0.2147 | 0.512 | -0.419 | 0.675 | -1.218 | 0.789 |
ar.L4 | -0.2042 | 0.361 | -0.565 | 0.572 | -0.912 | 0.504 |
ar.L5 | -0.3155 | 0.355 | -0.888 | 0.374 | -1.012 | 0.381 |
ar.L6 | -0.1528 | 0.445 | -0.343 | 0.731 | -1.025 | 0.719 |
ar.L7 | 0.3206 | 0.299 | 1.073 | 0.283 | -0.265 | 0.906 |
ma.L1 | -0.3643 | 0.718 | -0.507 | 0.612 | -1.772 | 1.044 |
sigma2 | 285.1821 | 16.448 | 17.339 | 0.000 | 252.945 | 317.419 |
Ljung-Box (Q): | 20.47 | Jarque-Bera (JB): | 947.18 |
---|---|---|---|
Prob(Q): | 1.00 | Prob(JB): | 0.00 |
Heteroskedasticity (H): | 1.79 | Skew: | 2.22 |
Prob(H) (two-sided): | 0.05 | Kurtosis: | 14.85 |
res = arima.resid
fig,ax = plt.subplots(2,1,figsize=(15,8))
fig = sm.graphics.tsa.plot_acf(res, lags=50, ax=ax[0])
fig = sm.graphics.tsa.plot_pacf(res, lags=50, ax=ax[1])
plt.show()
pred = arima.predict(tr_start,te_end)[1:]
fig = plt.figure(figsize=(12, 9))
ax = fig.add_subplot(1, 1, 1, facecolor="10")
plt.plot(tes.index,tes, 'r', label='Série Real')
plt.plot(tes.index,pred[te_start:te_end],'g', label='Série prevista pelo modelo')
plt.legend(loc='upper left')
plt.xlim(te_start,te_end)
plt.title('Série temporal do faturamento da Hotmart no período analisado', fontsize=16)
plt.xlabel('Tempo em dias', fontsize=14)
plt.ylabel('Faturamento em unidades do produto mais caro', fontsize=14)
Text(0, 0.5, 'Faturamento em unidades do produto mais caro')
from sklearn.metrics import mean_squared_error
pred = arima.predict(tr_start,te_end)[1:]
real = sales_timeSeries.iloc[1:]
print('ARIMA model MSE:{}'.format(mean_squared_error(tes,pred[te_start:te_end])))
ARIMA model MSE:209.95690860991186