O Hackathon Brasal/PCTec-UnB 2019 foi uma maratona de dados (dias 9 e 10 de maio de 2019), que reuniu estudantes, profissionais e comunidade, com o desafio de em dois dias, realizaram um projeto de Bussiness Intelligence para um cliente real: Brasal Veículos. Aconteceu no CDT da Universidade de Brasília (UnB) no Brasil.
Nesse contexto, minha equipe desenvolveu o projeto "Vendedor IA" (VIA), um conjunto de modelos de Inteligência Artificial (IA) usando o Deep Learning cujo princípio é descrito nos parágrafos seguintes.
Vendedor IA (VIA)
O VIA é uma ferramenta de IA (um conjunto de modelos usando o Deep Learning) que ajuda os vendedores de Brasal Veículos a atingir a suas metas de vendas.
Ele não é um avatar que vende diretamente os veículos. Graças ao treinamento com os dados de vendas do passado, faz previsões personalisadas em função do perfil de cada cliente. Por exemplo: estimativa do orçamento que o cliente está disposto a gastar na compra de um veículo, previsão da categoria de veículo que o cliente mais gosta, previsão da data de compra mais provável, etc.).
VIA modeliza o mundo cliente-vendedor
Graças ao treinamento com os dados de vendas do passado, as redes neurais do VIA modelizam o mundo cliente-vendedor no contexto das vendas de veículos Brasal.
Treinamento dos modelos de IA
Os modelos de IA são treinados a partir dos dados de vendas de veículos Brasal dos últimos anos e são atualizados todas as noites com os dados de vendas do dia.
Modelos desenvolvidos no contexto do hackathon
Muitos modelos AI podem ser desenvolvidos para o VIA (veja exemplos dados). Apenas para mostrar como, um modelo de regressão fornecendo o orçamento que o cliente está disposto a gastar na compra de um veículo está disponível online no github (vendedor_IA_vendas_veiculos_brasal_REGRESSAO.ipynb).
AI4BI (Artificial Intelligence for Business Intelligence)
O VIA é também um projeto AI4BI: as previsões do VIA vão para um aplicativo de BI que permite aos vendedores de Brasal Veículos de visualizá-las em gráficos interativos.
2 jupyter notebooks foram criados:
%reload_ext autoreload
%autoreload 2
from fastai.basics import *
import fastai
print(f'fastai: {fastai.__version__}')
print(f'cuda: {torch.cuda.is_available()}')
fastai: 1.0.52 cuda: True
PATH = Config.data_path()/'brasal/'
# PATH.ls()
Para criar o modelo de probabilidades de vendas por categoria de veículo, usamos os seguintes arquivos.
Nota: o arquivo MODELOS_VEICULOS.CSV
foi criado a partir do VEICULOS_BRAVOS.CSV
com a coluna CLASSIFICACAO
em adição, o que dá a categoria do veículo.
# list of tables
table_names = ['CLIENTES_BRAVOS',
'MODELOS_VEICULOS',
'VENDAS_VEICULOS_BRAVOS',
'VENDEDOR_BRAVOS']
tables = [pd.read_csv(PATH/f'{fname}.csv', low_memory=False, sep=";", encoding='cp1252') for fname in table_names]
clientes, veiculos, vendas, vendedores = tables
print(f'{len(tables)} tabelas: clientes, veiculos, vendas, vendedores')
print(f'Número de vendas: {len(vendas)}')
4 tabelas: clientes, veiculos, vendas, vendedores Número de vendas: 7284
Processo:
df = clientes
df.columns.values
array(['CLIENTE_COD', 'NOME_CLIENTE', 'DATA_NASCIMENTO', 'SEXO', 'ESTADO_CIVIL', 'DESC_ESTADO_CIVIL', 'SITUACAO_CREDITO', 'CLIENTE_TIPO', 'CLIENTE_CATEGORIA', 'DES_CATEGORIA_CLIENTE', 'ENDERECO_BAIRRO', 'ENDERECO_MUNICIPIO', 'ENDERECO_UF', 'CEP', 'CLIENTE_DTCADASTRO', 'CLIENTE_DTULTIMOMOVIMENTO', 'DTULTIMAALTERACAO', 'DTPRIMEIRAFATURA', 'DTULTIMAFATURA', 'LIMITE_CREDITO', 'CLIENTE_VIP', 'EMPRESA_ORIGEM', 'REVENDA_ORIGEM', 'GRUPO_CLIENTE', 'CADEIRANTE', 'CLIENTE_INATIVO', 'TIPO_PESSOA'], dtype=object)
# List of fields by client
# client_tipo means
# 1 -> Ouro 2 -> Prata 3 -> Bronze E -> Especial L -> Lista Negra N -> Normal P -> Prospecção
columns_clientes = array(['CLIENTE_COD', 'DATA_NASCIMENTO', 'SEXO',
'ESTADO_CIVIL',
'SITUACAO_CREDITO',
'CEP',
'CLIENTE_TIPO', 'TIPO_PESSOA', # J / F
'CLIENTE_DTCADASTRO', 'EMPRESA_ORIGEM','REVENDA_ORIGEM',
'DTPRIMEIRAFATURA', 'DTULTIMAFATURA', 'LIMITE_CREDITO'
])
df_clientes = df[columns_clientes].copy()
# keep informations about client postal address by keeping the 3 first digits of CEP
df_clientes['CEP'] = df_clientes['CEP'].fillna(0.0).astype(np.int32)
for i in range(3):
f = lambda x: int(list(str(x))[i]) if len(list(str(x)))>=3 else 0
n = i+1
df_clientes[f'CEP{n}'] = df_clientes['CEP'].apply(f)
df_clientes = df_clientes.drop(['CEP'], axis=1)
# Remplace all date with error by '1900/01/01 00:00:00.000000000'
# check if value is NaN
def isNaN(num):
return num != num
def indexes_date_correction(date_fldname, df):
idxs_erros = list()
idxs_empty = list()
for idx,dt in df[date_fldname].iteritems():
if isNaN(dt) == False:
y = int(dt.split("/")[0])
m = int(dt.split("/")[1])
if (y < 1900) or (y > 2019) or (m < 1) or (m > 12) or (y == 2019 and m > 4):
idxs_erros.append(idx)
else:
idxs_empty.append(idx)
return idxs_erros, idxs_empty
def date_correction(idxs, date_fldname, df, new_date='1900/01/01 00:00:00.000000000', flag='error'):
df.loc[idxs, date_fldname] = new_date
if flag == 'error':
print(f'Todas as datas de {date_fldname} têm sido corrigidas.')
elif flag == 'empty':
print(f'Todas as cédulas sem data de {date_fldname} têm sido preenchidas.')
df = df_clientes
date_fldname = 'DATA_NASCIMENTO'
idxs_erros, idxs_empty = indexes_date_correction(date_fldname,df)
errors_data = len(idxs_erros)
empty_data = len(idxs_empty)
print(f'Número de cédulas sem datas {date_fldname}: {empty_data}')
print(f'Número de erros nas datas {date_fldname}: {errors_data}')
if errors_data != 0:
print()
print(df.loc[idxs_erros, date_fldname].head())
print()
print(df.loc[idxs_erros, date_fldname].tail())
Número de cédulas sem datas DATA_NASCIMENTO: 69531 Número de erros nas datas DATA_NASCIMENTO: 7764 7 2040/12/10 00:00:00.000000000 13 2045/11/08 00:00:00.000000000 16 2032/03/19 00:00:00.000000000 23 2049/11/18 00:00:00.000000000 24 2040/08/28 00:00:00.000000000 Name: DATA_NASCIMENTO, dtype: object 188002 0977/11/18 00:00:00.000000000 188217 0001/01/01 00:00:00.000000000 188253 2031/03/25 00:00:00.000000000 188405 2019/08/18 00:00:00.000000000 188407 2033/01/19 00:00:00.000000000 Name: DATA_NASCIMENTO, dtype: object
# Correction of date errors
date_correction(idxs_erros,date_fldname,df)
# Fillin empty cells with '1900/01/01 00:00:00.000000000'
date_correction(idxs_empty,date_fldname,df,flag='empty')
Todas as datas de DATA_NASCIMENTO têm sido corrigidas. Todas as cédulas sem data de DATA_NASCIMENTO têm sido preenchidas.
df = df_clientes
date_fldname = 'CLIENTE_DTCADASTRO'
idxs_erros, idxs_empty = indexes_date_correction(date_fldname,df)
errors_data = len(idxs_erros)
empty_data = len(idxs_empty)
print(f'Número de cédulas sem datas {date_fldname}: {empty_data}')
print(f'Número de erros nas datas {date_fldname}: {errors_data}')
if errors_data != 0:
print()
print(df.loc[idxs_erros, date_fldname].head())
print()
print(df.loc[idxs_erros, date_fldname].tail())
Número de cédulas sem datas CLIENTE_DTCADASTRO: 75946 Número de erros nas datas CLIENTE_DTCADASTRO: 247 94093 2038/05/23 00:00:00.000000000 101627 2043/06/24 00:00:00.000000000 101901 2047/12/15 00:00:00.000000000 101948 2043/12/14 00:00:00.000000000 101954 2049/10/20 00:00:00.000000000 Name: CLIENTE_DTCADASTRO, dtype: object 162595 2049/06/15 00:00:00.000000000 172822 2046/06/25 00:00:00.000000000 182667 2037/02/20 00:00:00.000000000 182675 2046/01/28 00:00:00.000000000 185408 2047/06/03 00:00:00.000000000 Name: CLIENTE_DTCADASTRO, dtype: object
# Correction of date errors
date_correction(idxs_erros,date_fldname,df)
# Fillin empty cells with '1900/01/01 00:00:00.000000000'
date_correction(idxs_empty,date_fldname,df,flag='empty')
Todas as datas de CLIENTE_DTCADASTRO têm sido corrigidas. Todas as cédulas sem data de CLIENTE_DTCADASTRO têm sido preenchidas.
df = df_clientes
date_fldname = 'DTPRIMEIRAFATURA'
idxs_erros, idxs_empty = indexes_date_correction(date_fldname,df)
errors_data = len(idxs_erros)
empty_data = len(idxs_empty)
print(f'Número de cédulas sem datas {date_fldname}: {empty_data}')
print(f'Número de erros nas datas {date_fldname}: {errors_data}')
if errors_data != 0:
print()
print(df.loc[idxs_erros, date_fldname].head())
print()
print(df.loc[idxs_erros, date_fldname].tail())
Número de cédulas sem datas DTPRIMEIRAFATURA: 177795 Número de erros nas datas DTPRIMEIRAFATURA: 0
# Correction of date errors
date_correction(idxs_erros,date_fldname,df)
# Fillin empty cells with '1900/01/01 00:00:00.000000000'
date_correction(idxs_empty,date_fldname,df,flag='empty')
Todas as datas de DTPRIMEIRAFATURA têm sido corrigidas. Todas as cédulas sem data de DTPRIMEIRAFATURA têm sido preenchidas.
df = df_clientes
date_fldname = 'DTULTIMAFATURA'
idxs_erros, idxs_empty = indexes_date_correction(date_fldname,df)
errors_data = len(idxs_erros)
empty_data = len(idxs_empty)
print(f'Número de cédulas sem datas {date_fldname}: {empty_data}')
print(f'Número de erros nas datas {date_fldname}: {errors_data}')
if errors_data != 0:
print()
print(df.loc[idxs_erros, date_fldname].head())
print()
print(df.loc[idxs_erros, date_fldname].tail())
Número de cédulas sem datas DTULTIMAFATURA: 177759 Número de erros nas datas DTULTIMAFATURA: 0
# Correction of date errors
date_correction(idxs_erros,date_fldname,df)
# Fillin empty cells with '1900/01/01 00:00:00.000000000'
date_correction(idxs_empty,date_fldname,df,flag='empty')
Todas as datas de DTULTIMAFATURA têm sido corrigidas. Todas as cédulas sem data de DTULTIMAFATURA têm sido preenchidas.
df_clientes.dtypes
CLIENTE_COD int64 DATA_NASCIMENTO object SEXO object ESTADO_CIVIL float64 SITUACAO_CREDITO object CLIENTE_TIPO object TIPO_PESSOA object CLIENTE_DTCADASTRO object EMPRESA_ORIGEM int64 REVENDA_ORIGEM int64 DTPRIMEIRAFATURA object DTULTIMAFATURA object LIMITE_CREDITO float64 CEP1 int64 CEP2 int64 CEP3 int64 dtype: object
# Delete espace in cells
f = lambda x: x.strip()
df_clientes.SEXO = df_clientes.SEXO.apply(f)
# Replace empty values by nan
df_clientes.SEXO.replace('', np.nan, inplace=True)
df_clientes['CLIENTE_COD'] = df_clientes['CLIENTE_COD'].astype(np.int32)
df_clientes['SEXO'] = df_clientes['SEXO'].fillna('N') # N = neutral when there is no sex
df_clientes['ESTADO_CIVIL'] = df_clientes['ESTADO_CIVIL'].fillna(0.0).astype(np.int32)
df_clientes['EMPRESA_ORIGEM'] = df_clientes['EMPRESA_ORIGEM'].astype(np.int32)
df_clientes['REVENDA_ORIGEM'] = df_clientes['REVENDA_ORIGEM'].astype(np.int32)
df_clientes['LIMITE_CREDITO'] = df_clientes['LIMITE_CREDITO'].fillna(0.0).astype(np.float32)
df_clientes['CEP1'] = df_clientes['CEP1'].astype(np.int32)
df_clientes['CEP2'] = df_clientes['CEP2'].astype(np.int32)
df_clientes['CEP3'] = df_clientes['CEP3'].astype(np.int32)
df_clientes['DATA_NASCIMENTO'] = pd.to_datetime(df_clientes['DATA_NASCIMENTO'], infer_datetime_format=True)
df_clientes['CLIENTE_DTCADASTRO'] = pd.to_datetime(df_clientes['CLIENTE_DTCADASTRO'], infer_datetime_format=True)
df_clientes['DTPRIMEIRAFATURA'] = pd.to_datetime(df_clientes['DTPRIMEIRAFATURA'], infer_datetime_format=True)
df_clientes['DTULTIMAFATURA'] = pd.to_datetime(df_clientes['DTULTIMAFATURA'], infer_datetime_format=True)
df_clientes.dtypes
CLIENTE_COD int32 DATA_NASCIMENTO datetime64[ns] SEXO object ESTADO_CIVIL int32 SITUACAO_CREDITO object CLIENTE_TIPO object TIPO_PESSOA object CLIENTE_DTCADASTRO datetime64[ns] EMPRESA_ORIGEM int32 REVENDA_ORIGEM int32 DTPRIMEIRAFATURA datetime64[ns] DTULTIMAFATURA datetime64[ns] LIMITE_CREDITO float32 CEP1 int32 CEP2 int32 CEP3 int32 dtype: object
df_clientes.head().T
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
CLIENTE_COD | 1 | 70 | 71 | 72 | 73 |
DATA_NASCIMENTO | 1900-01-01 00:00:00 | 1966-04-24 00:00:00 | 1966-05-21 00:00:00 | 1964-09-09 00:00:00 | 1968-09-07 00:00:00 |
SEXO | N | M | F | M | M |
ESTADO_CIVIL | 0 | 2 | 1 | 2 | 2 |
SITUACAO_CREDITO | Aprovado | Aprovado | Aprovado | Aprovado | Aprovado |
CLIENTE_TIPO | N | N | N | N | N |
TIPO_PESSOA | J | F | F | F | F |
CLIENTE_DTCADASTRO | 2015-02-24 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 |
EMPRESA_ORIGEM | 1 | 1 | 1 | 1 | 1 |
REVENDA_ORIGEM | 1 | 1 | 1 | 1 | 1 |
DTPRIMEIRAFATURA | 2015-04-01 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 |
DTULTIMAFATURA | 2016-09-30 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 |
LIMITE_CREDITO | 1e+07 | 0 | 0 | 0 | 0 |
CEP1 | 7 | 7 | 7 | 7 | 7 |
CEP2 | 7 | 7 | 7 | 7 | 7 |
CEP3 | 0 | 5 | 0 | 0 | 0 |
print(f'Número de clientes: {len(df_clientes)}')
Número de clientes: 188432
Processo: mantendo apenas as colunas necessárias para treinamento.
df = veiculos
df.columns.values
array(['EMPRESA', 'MODELO', 'DES_MODELO', 'FAMILIA', 'DES_FAMILIA', 'MARCA', 'CLASSIFICACAO'], dtype=object)
# List of fields by car
# We did not keep the empresa that owns the car and we classify in the same category new and second-hand cars
columns_veiculos = array(['MODELO', 'DES_MODELO', 'FAMILIA', 'MARCA', 'CLASSIFICACAO'])
df_veiculos = df[columns_veiculos].copy()
# Delete espace in cells
f = lambda x: x.strip()
df_veiculos.MODELO = df_veiculos.MODELO.apply(f)
df_veiculos.dtypes
MODELO object DES_MODELO object FAMILIA int64 MARCA object CLASSIFICACAO int64 dtype: object
df_veiculos['FAMILIA'] = df_veiculos['FAMILIA'].astype(np.int32)
df_veiculos['CLASSIFICACAO'] = df_veiculos['CLASSIFICACAO'].fillna(0).astype(np.int32)
df_veiculos.dtypes
MODELO object DES_MODELO object FAMILIA int32 MARCA object CLASSIFICACAO int32 dtype: object
df_veiculos.head().T
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
MODELO | .... | 1623H3 | 1624N3 | 1624U3 | 1632F2 |
DES_MODELO | FOX CONNECT SB | VIRTUS MSI 1.6 | VW/GOL 1.0 | GOL 1.6 | GOL 1,6 |
FAMILIA | 11 | 107 | 1 | 1 | 1 |
MARCA | VW | VW | VW | VW | VW |
CLASSIFICACAO | 1 | 3 | 1 | 1 | 1 |
print(f'Número de veiculos: {len(df_veiculos)}')
Número de veiculos: 592
print(f'Número de família de veiculos: {len(df_veiculos.FAMILIA.unique())}')
Número de família de veiculos: 37
Este arquivo de vendedores deveria fornecer mais informações sobre eles como sexo, idade etc. e como o local de trabalho do vendedor é indicado na tabela de vendas, não é necessário usar esse arquivo.
df = vendedores
df.columns.values
array(['VENDEDOR', 'NOME', 'EMPRESA', 'REVENDA', 'DEPARTAMENTO', 'FUNCAO', 'COD_FUNCAO', 'ATIVO'], dtype=object)
# List of fields by seller
columns_vendedores = array(['VENDEDOR',
'EMPRESA', 'REVENDA'
])
df_vendedores = df[columns_vendedores].copy()
df_vendedores.dtypes
VENDEDOR int64 EMPRESA int64 REVENDA int64 dtype: object
df_vendedores.head().T
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
VENDEDOR | 110001 | 110002 | 110003 | 110004 | 110005 |
EMPRESA | 1 | 1 | 1 | 1 | 1 |
REVENDA | 1 | 1 | 1 | 1 | 1 |
print(f'Número de vendedores: {len(df_vendedores)}')
Número de vendedores: 447
Processo:
df = vendas
df.columns.values
array(['EMPRESA', 'REVENDA', 'NUMERO_NOTA_FISCAL', 'SERIE_NOTA_FISCAL', 'TIPO_TRANSACAO', 'DTA_ENTRADA_SAIDA', 'DTA_DOCUMENTO', 'CLIENTE', 'DEPARTAMENTO', 'USUARIO', 'REVENDA_ORIGEM', 'QUANTIDADE', 'VAL_FRETE', 'VAL_DESCONTO', 'VAL_SEGURO', 'VAL_IPI', 'VAL_PIS', 'VAL_COFINS', 'VAL_TOTAL_REAL_ITEM', 'VAL_TOTAL_NOTA_ITEM', 'VAL_ICMS_RETIDO', 'VAL_CUSTO_CONTABIL', 'VAL_ICMS', 'VENDEDOR', 'VAL_ISS_RETIDO', 'COD_SETOR_VENDA', 'VAL_BONUS', 'MODELO'], dtype=object)
# List of fields by sale
columns_vendas = array(['EMPRESA', 'REVENDA',
'DTA_ENTRADA_SAIDA',
'CLIENTE',
'VAL_TOTAL_REAL_ITEM',
'VAL_TOTAL_NOTA_ITEM',
'VAL_CUSTO_CONTABIL',
'VENDEDOR',
'VAL_BONUS',
'MODELO'
])
df_vendas = df[columns_vendas].copy()
# Rename columns names to avoid problem when merging with table of sales
df_vendas.rename(columns={"EMPRESA": 'EMPRESA_VENDAS', 'REVENDA': 'REVENDA_VENDAS'}, inplace=True)
df = df_vendas
date_fldname = 'DTA_ENTRADA_SAIDA'
idxs_erros, idxs_empty = indexes_date_correction(date_fldname,df)
errors_data = len(idxs_erros)
empty_data = len(idxs_empty)
print(f'Número de cédulas sem datas {date_fldname}: {empty_data}')
print(f'Número de erros nas datas {date_fldname}: {errors_data}')
if errors_data != 0:
print()
print(df.loc[idxs_erros, date_fldname].head())
print()
print(df.loc[idxs_erros, date_fldname].tail())
Número de cédulas sem datas DTA_ENTRADA_SAIDA: 0 Número de erros nas datas DTA_ENTRADA_SAIDA: 3 6600 2201/11/16 00:00:00.000 7035 2019/12/20 00:00:00.000 7109 2019/12/07 00:00:00.000 Name: DTA_ENTRADA_SAIDA, dtype: object 6600 2201/11/16 00:00:00.000 7035 2019/12/20 00:00:00.000 7109 2019/12/07 00:00:00.000 Name: DTA_ENTRADA_SAIDA, dtype: object
# Correction of date errors
df.loc[6600,'DTA_ENTRADA_SAIDA'] = '2001/11/16 00:00:00.000'
df.loc[7035,'DTA_ENTRADA_SAIDA'] = '2009/12/20 00:00:00.000'
df.loc[7109,'DTA_ENTRADA_SAIDA'] = '2009/12/07 00:00:00.000'
df_vendas['VAL_TOTAL_REAL_ITEM'] = df_vendas['VAL_TOTAL_REAL_ITEM'].abs()
df_vendas['VAL_TOTAL_NOTA_ITEM'] = df_vendas['VAL_TOTAL_NOTA_ITEM'].abs()
df_vendas['VAL_CUSTO_CONTABIL'] = df_vendas['VAL_CUSTO_CONTABIL'].abs()
(df_vendas.VAL_TOTAL_REAL_ITEM < 0.).sum()
0
df_vendas.dtypes
EMPRESA_VENDAS int64 REVENDA_VENDAS int64 DTA_ENTRADA_SAIDA object CLIENTE int64 VAL_TOTAL_REAL_ITEM float64 VAL_TOTAL_NOTA_ITEM float64 VAL_CUSTO_CONTABIL float64 VENDEDOR int64 VAL_BONUS float64 MODELO object dtype: object
df_vendas['EMPRESA_VENDAS'] = df_vendas['EMPRESA_VENDAS'].astype(np.int32)
df_vendas['REVENDA_VENDAS'] = df_vendas['REVENDA_VENDAS'].astype(np.int32)
df_vendas['CLIENTE'] = df_vendas['CLIENTE'].astype(np.int32)
df_vendas['VENDEDOR'] = df_vendas['VENDEDOR'].astype(np.int32)
df_vendas['DTA_ENTRADA_SAIDA'] = pd.to_datetime(df_vendas['DTA_ENTRADA_SAIDA'], infer_datetime_format=True)
df_vendas.dtypes
EMPRESA_VENDAS int32 REVENDA_VENDAS int32 DTA_ENTRADA_SAIDA datetime64[ns] CLIENTE int32 VAL_TOTAL_REAL_ITEM float64 VAL_TOTAL_NOTA_ITEM float64 VAL_CUSTO_CONTABIL float64 VENDEDOR int32 VAL_BONUS float64 MODELO object dtype: object
df_vendas.head().T
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
EMPRESA_VENDAS | 1 | 1 | 1 | 5 | 1 |
REVENDA_VENDAS | 1 | 1 | 1 | 1 | 1 |
DTA_ENTRADA_SAIDA | 2015-03-25 00:00:00 | 2015-03-31 00:00:00 | 2015-04-30 00:00:00 | 2018-09-18 00:00:00 | 2015-05-14 00:00:00 |
CLIENTE | 52302 | 1 | 1 | 51804 | 1 |
VAL_TOTAL_REAL_ITEM | 48800 | 37008.1 | 37043.2 | 41869.3 | 127944 |
VAL_TOTAL_NOTA_ITEM | 48800 | 37008.1 | 37043.2 | 41869.3 | 127944 |
VAL_CUSTO_CONTABIL | 47713.8 | 37008.1 | 37043.2 | 41869.3 | 127944 |
VENDEDOR | 110004 | 110011 | 110011 | 4000020 | 110011 |
VAL_BONUS | NaN | NaN | NaN | NaN | NaN |
MODELO | 5U4FE4 | 6A53K4 | 5U3PN4 | 5U7TA4 | 2HBB3A |
print(f'Número de vendas: {len(df_vendas)}')
Número de vendas: 7284
# save tables
df_tables = [df_clientes, df_veiculos, df_vendas, df_vendedores]
with open(PATH/'df_tables', 'wb') as f:
pickle.dump(df_tables, f)
# load file
file = open(PATH/'df_tables','rb')
df_tables = pickle.load(file)
df_clientes, df_veiculos, df_vendas, df_vendedores = df_tables
file.close()
# function to merge 2 dataframes
def join_df(left, right, left_on, right_on=None, suffix='_y'):
if right_on is None: right_on = left_on
return left.merge(right, how='left', left_on=left_on, right_on=right_on,
suffixes=("", suffix))
df_vendas = join_df(df_vendas, df_clientes, "CLIENTE", "CLIENTE_COD")
df_vendas = join_df(df_vendas, df_veiculos, "MODELO", "MODELO")
df_vendas = df_vendas.drop(['CLIENTE_COD'], axis=1)
df_vendas.dtypes
EMPRESA_VENDAS int32 REVENDA_VENDAS int32 DTA_ENTRADA_SAIDA datetime64[ns] CLIENTE int32 VAL_TOTAL_REAL_ITEM float64 VAL_TOTAL_NOTA_ITEM float64 VAL_CUSTO_CONTABIL float64 VENDEDOR int32 VAL_BONUS float64 MODELO object DATA_NASCIMENTO datetime64[ns] SEXO object ESTADO_CIVIL int32 SITUACAO_CREDITO object CLIENTE_TIPO object TIPO_PESSOA object CLIENTE_DTCADASTRO datetime64[ns] EMPRESA_ORIGEM int32 REVENDA_ORIGEM int32 DTPRIMEIRAFATURA datetime64[ns] DTULTIMAFATURA datetime64[ns] LIMITE_CREDITO float32 CEP1 int32 CEP2 int32 CEP3 int32 DES_MODELO object FAMILIA float64 MARCA object CLASSIFICACAO float64 dtype: object
df_vendas.head().T
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
EMPRESA_VENDAS | 1 | 1 | 1 | 5 | 1 |
REVENDA_VENDAS | 1 | 1 | 1 | 1 | 1 |
DTA_ENTRADA_SAIDA | 2015-03-25 00:00:00 | 2015-03-31 00:00:00 | 2015-04-30 00:00:00 | 2018-09-18 00:00:00 | 2015-05-14 00:00:00 |
CLIENTE | 52302 | 1 | 1 | 51804 | 1 |
VAL_TOTAL_REAL_ITEM | 48800 | 37008.1 | 37043.2 | 41869.3 | 127944 |
VAL_TOTAL_NOTA_ITEM | 48800 | 37008.1 | 37043.2 | 41869.3 | 127944 |
VAL_CUSTO_CONTABIL | 47713.8 | 37008.1 | 37043.2 | 41869.3 | 127944 |
VENDEDOR | 110004 | 110011 | 110011 | 4000020 | 110011 |
VAL_BONUS | NaN | NaN | NaN | NaN | NaN |
MODELO | 5U4FE4 | 6A53K4 | 5U3PN4 | 5U7TA4 | 2HBB3A |
DATA_NASCIMENTO | 1977-09-09 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 |
SEXO | M | N | N | N | N |
ESTADO_CIVIL | 2 | 0 | 0 | 0 | 0 |
SITUACAO_CREDITO | Aprovado | Aprovado | Aprovado | Aprovado | Aprovado |
CLIENTE_TIPO | N | N | N | N | N |
TIPO_PESSOA | F | J | J | J | J |
CLIENTE_DTCADASTRO | 1900-01-01 00:00:00 | 2015-02-24 00:00:00 | 2015-02-24 00:00:00 | 1900-01-01 00:00:00 | 2015-02-24 00:00:00 |
EMPRESA_ORIGEM | 1 | 1 | 1 | 1 | 1 |
REVENDA_ORIGEM | 1 | 1 | 1 | 1 | 1 |
DTPRIMEIRAFATURA | 2015-03-02 00:00:00 | 2015-04-01 00:00:00 | 2015-04-01 00:00:00 | 2015-03-25 00:00:00 | 2015-04-01 00:00:00 |
DTULTIMAFATURA | 2015-03-25 00:00:00 | 2016-09-30 00:00:00 | 2016-09-30 00:00:00 | 2019-03-07 00:00:00 | 2016-09-30 00:00:00 |
LIMITE_CREDITO | 0 | 1e+07 | 1e+07 | 0 | 1e+07 |
CEP1 | 7 | 7 | 7 | 7 | 7 |
CEP2 | 7 | 7 | 7 | 7 | 7 |
CEP3 | 5 | 0 | 0 | 8 | 0 |
DES_MODELO | FRONTIER | FIAT SIENA | VOYAGE 1.6 | VW/FOX 1.6 PIME GII | PEUGEOT 207 PASSION XS A |
FAMILIA | 1 | 1 | 1 | 1 | 58 |
MARCA | VW | VW | VW | VW | PEUG |
CLASSIFICACAO | 3 | 1 | 3 | 1 | 1 |
print(f'Número de vendas: {len(df_vendas)}')
Número de vendas: 7284
# save file
df_vendas.to_pickle(PATH/'df_vendas')
The following extracts particular date fields from a complete datetime for the purpose of constructing categoricals.
You should always consider this feature extraction step when working with date-time. Without expanding your date-time into these additional fields, you can't capture any trend/cyclical behavior as a function of time at any of these granularities. We'll add to every table with a date field.
# load file
file = open(PATH/'df_vendas','rb')
df_vendas = pickle.load(file)
file.close()
def add_datepart(df, fldname, drop=True, time=False):
"Helper function that adds columns relevant to a date."
fld = df[fldname]
fld_dtype = fld.dtype
if isinstance(fld_dtype, pd.core.dtypes.dtypes.DatetimeTZDtype):
fld_dtype = np.datetime64
if not np.issubdtype(fld_dtype, np.datetime64):
df[fldname] = fld = pd.to_datetime(fld, infer_datetime_format=True)
targ_pre = re.sub('[Dd]ate$', '', fldname)
attr = ['Year', 'Month', 'Week', 'Day', 'Dayofweek', 'Dayofyear',
'Is_month_end', 'Is_month_start', 'Is_quarter_end', 'Is_quarter_start', 'Is_year_end', 'Is_year_start']
if time: attr = attr + ['Hour', 'Minute', 'Second']
for n in attr: df[targ_pre + '_' + n] = getattr(fld.dt, n.lower())
df[targ_pre + '_' + 'Elapsed'] = fld.astype(np.int64) // 10 ** 9
if drop: df.drop(fldname, axis=1, inplace=True)
add_datepart(df_vendas, "DTA_ENTRADA_SAIDA", drop=False)
add_datepart(df_vendas, "DATA_NASCIMENTO", drop=False)
add_datepart(df_vendas, "CLIENTE_DTCADASTRO", drop=False)
add_datepart(df_vendas, "DTPRIMEIRAFATURA", drop=False)
add_datepart(df_vendas, "DTULTIMAFATURA", drop=False)
df_vendas.head().T
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
EMPRESA_VENDAS | 1 | 1 | 1 | 5 | 1 |
REVENDA_VENDAS | 1 | 1 | 1 | 1 | 1 |
DTA_ENTRADA_SAIDA | 2015-03-25 00:00:00 | 2015-03-31 00:00:00 | 2015-04-30 00:00:00 | 2018-09-18 00:00:00 | 2015-05-14 00:00:00 |
CLIENTE | 52302 | 1 | 1 | 51804 | 1 |
VAL_TOTAL_REAL_ITEM | 48800 | 37008.1 | 37043.2 | 41869.3 | 127944 |
VAL_TOTAL_NOTA_ITEM | 48800 | 37008.1 | 37043.2 | 41869.3 | 127944 |
VAL_CUSTO_CONTABIL | 47713.8 | 37008.1 | 37043.2 | 41869.3 | 127944 |
VENDEDOR | 110004 | 110011 | 110011 | 4000020 | 110011 |
VAL_BONUS | NaN | NaN | NaN | NaN | NaN |
MODELO | 5U4FE4 | 6A53K4 | 5U3PN4 | 5U7TA4 | 2HBB3A |
DATA_NASCIMENTO | 1977-09-09 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 | 1900-01-01 00:00:00 |
SEXO | M | N | N | N | N |
ESTADO_CIVIL | 2 | 0 | 0 | 0 | 0 |
SITUACAO_CREDITO | Aprovado | Aprovado | Aprovado | Aprovado | Aprovado |
CLIENTE_TIPO | N | N | N | N | N |
TIPO_PESSOA | F | J | J | J | J |
CLIENTE_DTCADASTRO | 1900-01-01 00:00:00 | 2015-02-24 00:00:00 | 2015-02-24 00:00:00 | 1900-01-01 00:00:00 | 2015-02-24 00:00:00 |
EMPRESA_ORIGEM | 1 | 1 | 1 | 1 | 1 |
REVENDA_ORIGEM | 1 | 1 | 1 | 1 | 1 |
DTPRIMEIRAFATURA | 2015-03-02 00:00:00 | 2015-04-01 00:00:00 | 2015-04-01 00:00:00 | 2015-03-25 00:00:00 | 2015-04-01 00:00:00 |
DTULTIMAFATURA | 2015-03-25 00:00:00 | 2016-09-30 00:00:00 | 2016-09-30 00:00:00 | 2019-03-07 00:00:00 | 2016-09-30 00:00:00 |
LIMITE_CREDITO | 0 | 1e+07 | 1e+07 | 0 | 1e+07 |
CEP1 | 7 | 7 | 7 | 7 | 7 |
CEP2 | 7 | 7 | 7 | 7 | 7 |
CEP3 | 5 | 0 | 0 | 8 | 0 |
DES_MODELO | FRONTIER | FIAT SIENA | VOYAGE 1.6 | VW/FOX 1.6 PIME GII | PEUGEOT 207 PASSION XS A |
FAMILIA | 1 | 1 | 1 | 1 | 58 |
MARCA | VW | VW | VW | VW | PEUG |
CLASSIFICACAO | 3 | 1 | 3 | 1 | 1 |
DTA_ENTRADA_SAIDA_Year | 2015 | 2015 | 2015 | 2018 | 2015 |
... | ... | ... | ... | ... | ... |
CLIENTE_DTCADASTRO_Is_quarter_start | True | False | False | True | False |
CLIENTE_DTCADASTRO_Is_year_end | False | False | False | False | False |
CLIENTE_DTCADASTRO_Is_year_start | True | False | False | True | False |
CLIENTE_DTCADASTRO_Elapsed | -2208988800 | 1424736000 | 1424736000 | -2208988800 | 1424736000 |
DTPRIMEIRAFATURA_Year | 2015 | 2015 | 2015 | 2015 | 2015 |
DTPRIMEIRAFATURA_Month | 3 | 4 | 4 | 3 | 4 |
DTPRIMEIRAFATURA_Week | 10 | 14 | 14 | 13 | 14 |
DTPRIMEIRAFATURA_Day | 2 | 1 | 1 | 25 | 1 |
DTPRIMEIRAFATURA_Dayofweek | 0 | 2 | 2 | 2 | 2 |
DTPRIMEIRAFATURA_Dayofyear | 61 | 91 | 91 | 84 | 91 |
DTPRIMEIRAFATURA_Is_month_end | False | False | False | False | False |
DTPRIMEIRAFATURA_Is_month_start | False | True | True | False | True |
DTPRIMEIRAFATURA_Is_quarter_end | False | False | False | False | False |
DTPRIMEIRAFATURA_Is_quarter_start | False | True | True | False | True |
DTPRIMEIRAFATURA_Is_year_end | False | False | False | False | False |
DTPRIMEIRAFATURA_Is_year_start | False | False | False | False | False |
DTPRIMEIRAFATURA_Elapsed | 1425254400 | 1427846400 | 1427846400 | 1427241600 | 1427846400 |
DTULTIMAFATURA_Year | 2015 | 2016 | 2016 | 2019 | 2016 |
DTULTIMAFATURA_Month | 3 | 9 | 9 | 3 | 9 |
DTULTIMAFATURA_Week | 13 | 39 | 39 | 10 | 39 |
DTULTIMAFATURA_Day | 25 | 30 | 30 | 7 | 30 |
DTULTIMAFATURA_Dayofweek | 2 | 4 | 4 | 3 | 4 |
DTULTIMAFATURA_Dayofyear | 84 | 274 | 274 | 66 | 274 |
DTULTIMAFATURA_Is_month_end | False | True | True | False | True |
DTULTIMAFATURA_Is_month_start | False | False | False | False | False |
DTULTIMAFATURA_Is_quarter_end | False | True | True | False | True |
DTULTIMAFATURA_Is_quarter_start | False | False | False | False | False |
DTULTIMAFATURA_Is_year_end | False | False | False | False | False |
DTULTIMAFATURA_Is_year_start | False | False | False | False | False |
DTULTIMAFATURA_Elapsed | 1427241600 | 1475193600 | 1475193600 | 1551916800 | 1475193600 |
94 rows × 5 columns
Next we'll fill in missing values to avoid complications with NA
's. NA
(not available) is how Pandas indicates missing values; many models have problems when missing values are present, so it's always important to think about how to deal with them. In these cases, we are picking an arbitrary signal value that doesn't otherwise appear in the data.
list_col = list(df_vendas.columns.values)
# list columns with missing values
for col in list_col:
s = df_vendas[col].isna().sum()
# n = (df_vendas[col] == '').sum()
if s != 0 :
print(col)
VAL_BONUS DES_MODELO FAMILIA MARCA CLASSIFICACAO
df_vendas['VAL_BONUS'] = df_vendas.VAL_BONUS.fillna(0.0).astype(np.float32)
df_vendas['FAMILIA'] = df_vendas['FAMILIA'].fillna(1.0).astype(np.int32)
df_vendas['CLASSIFICACAO'] = df_vendas['CLASSIFICACAO'].fillna(1.0).astype(np.int32)
# list columns with missing values
for col in list_col:
s = df_vendas[col].isna().sum()
# n = (df_vendas[col] == '').sum()
if s != 0 :
print(col)
DES_MODELO MARCA
# save file
df_vendas.to_pickle(PATH/'df_vendas_clean')
Nota: como não usaremos as colunas DES_MODELO
e MARCA
, seus processamentos não valem a pena.
É comum trabalhar com dados de séries temporais para extrair dados que explicam os relacionamentos entre linhas, em oposição a colunas, por exemplo:
Isso geralmente é difícil de fazer com a maioria das estruturas de manipulação de tabelas, pois elas são projetadas para trabalhar com relacionamentos entre colunas.
Assim, criamos uma nova coluna DTA_VENDA_CADASTRO_DaysElapsed
que fornece o número de dias entre o dia de venda (DTA_ENTRADA_SAIDA
) e o de cadastro (CLIENTE_DTCADASTRO
).
# load file
df_vendas = pd.read_pickle(PATH/'df_vendas_clean')
df_vendas['DTA_VENDA_CADASTRO_DaysElapsed'] = ((df_vendas.DTA_ENTRADA_SAIDA_Elapsed - df_vendas.CLIENTE_DTCADASTRO_Elapsed)/(24*60*60)).copy()
f = lambda x: int(x)
df_vendas['DTA_VENDA_CADASTRO_DaysElapsed'] = df_vendas['DTA_VENDA_CADASTRO_DaysElapsed'].apply(f)
df_vendas['DTA_VENDA_CADASTRO_DaysElapsed'] = df_vendas['DTA_VENDA_CADASTRO_DaysElapsed'].astype(np.int32)
# save file
df_vendas.to_pickle(PATH/'df_vendas_clean_DaysElapsed')