Criação de variáveis, também conhecido como Feature engineering, é o processo de gerar novas variáveis (ou features) a partir dos dados disponíveis. Dado que um modelo de machine learning irá aprender a partir dos dados que são fornecidos, esse processo se torna um dos passos mais cruciais na criação de um modelo. Construir variáveis relevantes pode ser a diferença entre ter um modelo com um ótimo desempenho ou não.

Em geral, a criação de novas variáveis depende bastante do conhecimento da área, intuição e capacidade de manipulação dos dados. Este processo pode ser extremamente tedioso e o resultado final está limitado tanto pela imaginação e criatividade humana quanto pelo tempo. Observamos nos últimos anos um aumento da quantidade de ferramentas criadas para automatizar o pipeline de criação de um projeto de machine learning, incluindo frameworks desenvolvidos para a automatização da criação de variáveis (ou automated feature engineering). Hoje vamos ver como utilizar uma dessas ferramentas.

Featuretools

Featuretools é uma biblioteca open source em python desenvolvida para a criação de variáveis de forma automática. Para entender como funciona essa biblioteca precisamos entender seus três componentes principais:

  • Entidades (ou entities)
  • Deep Feature Synthesis (DFS)
  • Feature primitives

Quando criamos novas variáveis utilizamos as informações que estão armazenadas em tabelas que são relevantes ao modelo. Assim se estivermos criando um modelo para prever qual a chance de um cliente atrasar um pagamento, por exemplo, iremos usar tabelas que contenham informações como histórico de pagamentos, dias de atraso, etc. Se estivermos construindo um modelo para prever qual tipo de produto um cliente tem mais chance de comprar iremos utilizar tabelas que contenham o histórico de transações, os tipos de produtos, data das transações, formas de pagamento, etc.

De forma simplificada, podemos considerar que uma entidade são essas tabelas com as informações armazenadas. Um grupo de tabelas com a relação de como as informações delas podem ser conectadas é chamado de entityset.

O Deep Feature Synthesis (DFS) é a espinha dorsal do featuretools. Ele foi desenvolvido em 2014 por membros do Laboratório de Ciência da Computação e Inteligência Artificial do MIT. Ele é o algoritmo responsável pelo processo de criação das novas variáveis.

Para criar as variáveis, o DFS utiliza funções matemáticas pré definidas para agrupar e transformar os dados. Essas funções são chamadas de feature primitives e podem ser classificadas em dois tipos:

  • Funções de transformação (ou transformation primitives)
  • Funções de agregação (ou aggregation primitives)

Funções de transformação são funções que transformam informações existentes em novos dados. Alguns exemplos de funções de transformação são as funções month, year e weekday. A partir de uma data elas retornam, respectivamente, qual o mês, o ano e o dia da semana da data em questão. Na figura a seguir temos um  exemplo dessas funções.


Já as funções de agregação irão agregar as informações a partir de alguma chave. Por exemplo, se tivermos uma tabela que contém as informações das transações de um cliente podemos utilizar funções de agregação para calcular qual o máximo, mínimo e a média das transações de cada cliente. Na figura a seguir temos um exemplo da aplicação das funções de agregação mencionadas.

Assim, para criar novas variáveis de forma automática, o featuretools irá utilizar o DFS para identificar quais features primitives ele pode aplicar em cada informação contida nas entidades que armazenam a informação e como essas informações podem ser agrupadas.

A melhor maneira de compreendermos mais a fundo esses conceitos é fazendo um exemplo na prática. Para isso vamos usar tabelas com as informações referentes às transações de clientes em um site de vendas (essas tabelas estão disponíveis no próprio pacote do featuretools para serem utilizadas como exemplo).

Antes de tudo, devemos importar a biblioteca do featuretools.

 

# Use este comando caso o featuretools ainda não esteja instalado
# ! pip install -U featuretools

import featuretools as ft


Agora precisamos carregar as tabelas com as informações que vamos utilizar.

 

# Conjunto de tabelas com as informações de transações de clientes no site
data = ft.demo.load_mock_customer()
df_customers = data[‘customers’]df_sessions = data[‘sessions’]df_transactions = data[‘transactions’]

df_products = data[‘products’]


O dataframe
df_customers contém informações de cadastro de cada cliente como CEP, data de nascimento e data de cadastro no site.


Em df_sessions temos as informações sobre todas as sessões iniciadas pelos clientes no site. Nessa tabela temos qual o aparelho utilizado para acessar o site e quando as sessões foram iniciadas.

 


A tabela df_transactions possui o histórico de todas as transações realizadas durante alguma sessão. Nela encontramos o código dos produtos, valor e hora das transações.


Finalmente, em df_products temos informações adicionais sobre cada um dos produtos vendidos no site.


Temos agora toda a informação necessária para definir a entityset que iremos utilizar. Neste caso as tabelas df_customers, df_sessions, df_transactions e df_products são as nossas entities. Cada entity deve possuir uma coluna com valores únicos (ou index), veremos mais adiante porque esse fato é importante. 

O parâmetro time_index especifica a coluna de cada tabela que diz a data em que a informação daquela linha ocorreu. Por exemplo, a coluna join_date na tabela df_customers nos diz a data em que a conta de cada cliente foi criada, e a coluna transaction_time na tabela df_transactions nos diz a data em que cada transação foi realizada. Essa informação será importante quando formos criar variáveis utilizando uma janela de tempo.

No código abaixo criamos a nossa entityset site e adicionamos a ela as quatro entidades relativas às tabelas que mencionamos.

# Criando a entityset
es = ft.EntitySet(‘site’)
# Adicionando a entidade da tabela df_customers
es = es.entity_from_dataframe(entity_id = ‘customers’,
dataframe = df_customers,
                                                          index = ‘customer_id’,
                                                          time_index = ‘join_date’)
# Adicionando a entidade da tabela df_sessions
es = es.entity_from_dataframe(entity_id = ‘sessions’,
                                                          dataframe = df_sessions,
                                                          index = ‘session_id’,
                                                          time_index = ‘session_start’)
# Adicionando a entidade da tabela df_transactions
es = es.entity_from_dataframe(entity_id = ‘transactions’,
                                                          dataframe = df_transactions,
                                                          index = ‘transaction_id’,
                                                          time_index = ‘transaction_time’)

# Adicionando a entidade da tabela df_products
es = es.entity_from_dataframe(entity_id = ‘products’,
                                                          dataframe = df_products,
                                                          index = ‘product_id’)


Precisamos agora definir as relações (ou relationships) entre as entidades do nosso entityset. Isso é importante pois irá dizer ao DFS como a informação de uma entidade deve ser associada às informações presentes nas outras entidades. As relações são definidas a partir de colunas presentes em duas entidades que contenham a mesma informação, por exemplo, a coluna de customer_id conecta a informação da entidade customers e com a entidade sessions.

São essas relações que permitem que as informações sejam agrupadas usando as funções primitivas para a criação de novas variáveis, e é por isso que é importante que cada tabela possua um index. Para agrupar as informações de forma que não aconteça ambiguidade com qual informação utilizar é importante que as relações sejam definidas sempre utilizando uma coluna que seja um index de pelo menos uma das entidades. Na figura a seguir temos uma representação da entityset site e de como as relações entre as entidades são definidas.


Vemos portanto que a coluna customer_id que estabelece uma relação entre a entidade customers e a entidade sessions, e essa coluna é o index da entidade customers. Da mesma forma session_id estabelece uma relação entre as entidades sessions e transactions, sendo o index de sessions. A seguir temos o código de como inserir essas relações na entityset site.

 

new_relationship1 = ft.Relationship(es[‘customers’][‘customer_id’],
                                                                    es[‘sessions’][‘customer_id’])
new_relationship2 = ft.Relationship(es[‘sessions’][‘session_id’],
                                                                    es[‘transactions’][‘session_id’])
new_relationship3 = ft.Relationship(es[‘products’][‘product_id’],
                                                                    es[‘transactions’][‘product_id’])
es = es.add_relationship(new_relationship1)
es = es.add_relationship(new_relationship2)
es = es.add_relationship(new_relationship3)


Agora estamos prontos para criar novas variáveis utilizando o DFS. Primeiro devemos definir qual entidade iremos utilizar no parâmetro target_entity. Isso irá dizer ao DFS com relação a qual informação ele irá agrupar os dados. Como estamos analisando o comportamento de compras de clientes em um site, queremos que a informação seja construída com relação ao comportamento de cada um dos clientes (quantas vezes eles acessaram o site, qual o valor médio das transações de cada cliente, etc). Assim devemos colocar o parâmetro target_entity = ‘customers’, isso irá dizer para o DFS agregar as informações com relação ao index da entidade customers, que nessa caso é o customer_id.

Além disso devemos dizer quais features primitives queremos que sejam utilizadas na criação das variáveis. Para o nosso exemplo iremos utilizar as funções de agregação mean, sum e mode (respectivamente: média, soma e moda), e as funções de transformação month e hour (que são funções que a partir de um timestamp retornam o mês e a hora daquele timestamp em questão). Podemos ver a lista de todas as primitivas disponíveis e suas descrições da seguinte forma.

 

# Dataframe com todas as primitivas disponíveis
ft.list_primitives()


Alguns exemplos de funções de agregação.

Alguns exemplos de funções de transformação.

Podemos agora utilizar o DFS com as primitivas que selecionamos para criar as variáveis relativas a cada cliente.

features, feature_names = ft.dfs(entityset = es,
                                                            target_entity = ‘customers’,
                                                            agg_primitives = [‘mean’, ‘sum’, ‘mode’],
                                                            trans_primitives = [‘month’,’hour’],
                                                            max_depth = 2)
features

Em feature_names conseguimos visualizar todas as variáveis que foram criadas pelo DFS.

feature_names

Para entender cada uma dessas novas variáveis devemos compreender como o nome delas são geradas. Vamos analisar a variável MEAN(transactions.amount). O que o algoritmo fez nessa variável foi pegar os valores da coluna amount na entidade transactions e calcular a média desses valores com relação a cada customer_id, ou seja, ela é o valor médio dos gastos de cada cliente em transações. Conseguimos identificar isso apenas pela forma em que o nome da variável foi criado.

 

Mas existem algumas variáveis em que aparece mais de um nome de feature primitive. Isso ocorre porque especificamos o parâmetro max_depth = 2. Esse parâmetro controla quantas vezes podemos empilhar funções primitivas em cada coluna. Assim, com max_depth = 2, sempre que possível o algoritmo irá empilhar duas feature primitives em cada coluna.

Podemos ver isso na variável MODE(sessions.HOUR(session_start)). Para construir essa variável o algoritmo aplicou a função de transformação HOUR na coluna session_start da entidade sessions e depois calculou a moda desses valores resultantes. Logo essa variável nos diz qual a hora mais frequente em que cada consumidor inicia a sessão no site.


Uma das grandes vantagens do featuretools é a possibilidade de utilizar o tempo na hora da criação das variáveis. Ao especificar uma data para o parâmetro cutoff_time o algoritmo irá utilizar apenas as informações que tenham ocorrida antes dessa data para a criação das variáveis. No código a seguir temos um exemplo disso.

 

features, feat_names = ft.dfs(entityset = es,
                                                       target_entity = ‘customers’,
                                                       cutoff_time = pd.Timestamp(“2014-01-08 00:00:00”),
                                                       agg_primitives = [‘mean’, ‘sum’, ‘mode’],
                                                       trans_primitives = [‘month’,’hour’],
                                                       max_depth = 2)
features


Vemos que na linha customer_id = 3 as variáveis estão com o valor NULL. Isso ocorre porque todas as sessões desse consumidor foram iniciadas depois do cutoff_time ( 2014-01-08), e  portanto todas as informações dessas sessões foram descartadas na hora da criação das variáveis.

 

Conclusão

Utilizar o featuretools para a criação de variáveis de forma automática economiza uma boa quantidade de tempo no pipeline de criação de um projeto de machine learning. Não à toa ele se tornou tão popular em hackatons e competições de machine learning. 

Para mais informações sobre o featuretools acesse: https://docs.featuretools.com/en/stable/