Olá novamente! Este é o segundo tutorial da série que tentará descobrir o algoritmo de valorização do Cartola FC. Se você ainda não leu o primeiro tutorial, recomendo leitura antes de ler este estudo. Nossos objetivos aqui são:
Além disso, você estudará análise de dados usando Python com Pandas, Seaborn, Sklearn. Espero que você tenha noção sobre:
In [132]:
# Importar bibliotecas
import pprint
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import lightgbm as lgb
from sklearn import linear_model
from sklearn.metrics import (mean_squared_error, r2_score)
from sklearn import ensemble
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import (AdaBoostRegressor, RandomForestRegressor)
pd.options.mode.chained_assignment = None
%matplotlib inline
pd.options.display.float_format = '{:,.2f}'.format
# Abrir banco de dados
dados = pd.read_csv('~/caRtola/data/desafio_valorizacao/valorizacao_cartola_2018.csv')
# Listar nome das variáveis
str(list(dados))
# Selecionar variáveis para estudo
dados = dados[['slug', 'rodada', 'posicao',
'status', 'variacao_preco', 'pontos',
'preco', 'media_pontos']]
# Visualizar dados
dados.head(n=10)
Out[132]:
Chegamos a etapa mais longa e que é crítica para modelagem de dados - preparação dos dados. Nós iremos repetir alguns passos do estudo 1, mas tentaremos aplicar inicialmente um modelo para todos os jogadores de uma dada posição. Nosso roteiro nestá sessão será:
In [133]:
# 1. Criar atributos para modelagem
def recode_position(data):
'''Recodificar posicao para númerica'''
PositionDictionary = {
'tec':0,
'gol':1,
'zag':2,
'lat':3,
'mei':4,
'ata':5
}
return pd.Series(data['posicao'].map(PositionDictionary))
def create_atributes(data):
'''Criar atributos com lags - variacao_preco_lag e pontos_lag'''
try:
data['variacao_preco_lag'] = data.groupby(['slug'])['variacao_preco'].shift(1)
data['pontos_lag'] = data.groupby(['slug'])['pontos'].shift(1)
data['jogou_partida'] = np.where(data.status.isin(['Nulo', 'Suspenso',
'Contundido', 'Dúvida']) &
data.pontos_lag==0,
0,
1)
data['posicao_rec'] = recode_position(data)
data = data.dropna(how='any')
return(data)
except:
print('Deu ruim - Function create_atributes failed')
# 2. Padronizar atributos
def agg_by_round(x):
'''Computar preco, pontuacao media e preco'''
names = {
'med_preco': x['preco'].mean(),
'med_pontos_lag': x['pontos_lag'].mean(),
'std_preco': x['preco'].std(),
'std_pontos_lag': x['pontos_lag'].std(),
'soma_pontos_lag_rp': x['pontos_lag'].sum(),
'soma_preco_rp': x['preco'].sum()
}
return pd.Series(names, index=['med_preco', 'med_pontos_lag','std_preco',
'std_pontos_lag','soma_pontos_lag_rp','soma_preco_rp'])
def center_atributes(data):
'''Centrar e escalar atributos dos dados'''
data['preco_cen'] = (data['preco'] - data['med_preco']) / data['std_preco']
data['pon_lag_cen'] = (data['pontos_lag'] - data['med_pontos_lag']) / data['std_pontos_lag']
data['pon_lag_ratio'] = data['pontos_lag'] / data['soma_pontos_lag_rp']
data['preco_ratio'] = data['preco'] / data['soma_preco_rp']
return data
In [134]:
# Processar dados
create_atributes(dados) # Criar atributos
agg_data = dados.groupby(['rodada','posicao']).apply(agg_by_round) # Criar atributos gerais por rodada
dados = dados.join(agg_data, on=['rodada','posicao']) # Join dataframes
dados = center_atributes(dados) # Centrar atributos
dados = dados.dropna() # Retirar dados omissos
dados.head() # Visualizar banco de dados transformado
Out[134]:
In [135]:
# Matriz de correlação
dados[['variacao_preco_lag', 'pontos_lag', 'pon_lag_cen',
'preco_cen', 'med_preco', 'preco',
'med_pontos_lag', 'media_pontos', 'preco_ratio', 'pon_lag_ratio']].corr()
Out[135]:
De acordo com a tabela acima, podemos observar que as seguintes variáveis estão correlacionadas com a variação de preço que estamos tentando prever. Em ordem de magnitude temos: pontos_lag, pon_lag_cen, preco_cen e preco. O resto das variáveis numéricas provavelmente não nos ajudarão no modelo.
Está chegando a hora. Usaremos a biblioteca sckitlearn. Para usar suas funções, precisaremos adaptar nosso banco de dados. Vamos aproveitar e escrever uma função para avaliar os modelos usando validação cruzada 'k-fold'. A ideia é evitar super ajustamento do nosso modelo aos dados.
Para avaliar nossos modelos, usaremos o erro médio quadrático, que é uma estimativa de quanto as predições de um dado modelo é próxima dos valores reais.
In [33]:
# Ver ordem das colunas do banco de dados
variables = list(dados)
variables = {variables[i]:i for i in range(0, len(variables))}
sorted_by_value = sorted(variables.items(), key=lambda kv: kv[1])
pprint.pprint(sorted_by_value)
In [43]:
# Converter data frame para matriz
train_data = dados.values
# Atributos: rodada, preco, media_pontos, pontos_lag, jogou_partida, posicao_rec, med_preco, med_pontos_lag
train_features = train_data[... , [1,6,7,9,10,11,12,13]]
train_result = train_data[... , 8]
def get_model_outcomes(model):
'''Função que calcula valores usando validação cruzada k-folds para um dado modelo'''
SEED=42
mean_rmse = 0.0
mean_r2 = 0.0
n = 10
for i in range(n):
X_train, X_cv, y_train, y_cv = train_test_split(
train_features, train_result, test_size=.20, random_state=i*SEED)
# Treinar modelo e realizar predições
model.fit(X_train, y_train)
preds = model.predict(X_cv)
print('[Fold %d/%d] Mean Squared Error: %.2f | R^2: %.2f' %
(i + 1, n, mean_squared_error(y_cv, preds), r2_score(y_cv, preds)))
mean_rmse += mean_squared_error(y_cv, preds)
mean_r2 += r2_score(y_cv, preds)
print('Mean RMSE: %.3f | Mean R^2 %.3f' % (mean_rmse/n, mean_r2/n))
Hora de testar a capacidade preditiva de quatro modelos. Começaremos simples com uma regressão linear, depois tentaremos usar árvores de decisão com adaBoost e Random Forest. Por fim, usaremos o algoritmo lightgbm, campeão em diversas competições. Ele é uma variação do Extreme Gradient Boosting que é mais computacionalmente mais eficiente.
Focaremos na métrica RMSE, erro médio quadrático. Para nós valores próximos a zero são desejáveis. Usaremos para os três primeiros modelos também o $R^2$.
In [44]:
# Linear regression
regr_0 = linear_model.LinearRegression()
get_model_outcomes(regr_0)
In [45]:
# AdaBoost
rng=42
regr_1 = AdaBoostRegressor(DecisionTreeRegressor(max_depth=8),
n_estimators=100, random_state=rng)
get_model_outcomes(regr_1)
In [9]:
# Random Forest
regr_2 = RandomForestRegressor(max_depth=8, random_state=0, n_estimators=100)
get_model_outcomes(regr_2)
In [47]:
# LightGBM
features_names = ['rodada', 'preco', 'media_pontos',
'pontos_lag', 'jogou_partida', 'posicao_rec',
'med_preco', 'med_pontos_lag']
train_data = lgb.Dataset(train_features, label=train_result,
feature_name=features_names,
categorical_feature=['jogou_partida','posicao_rec'],
free_raw_data=False)
test_data = lgb.Dataset('test.svm', reference=train_data)
params = {
'objective':'regression',
'metric':'mean_squared_error',
}
# Training
num_round = 500
cv_results = lgb.cv(params, train_data, num_round,
nfold=10,
stratified=False,
verbose_eval=20,
early_stopping_rounds=40)
In [48]:
# Display results
print('Current parameters:\n', params)
print('\nBest num_boost_round:', len(cv_results['l2-mean']))
print('Best CV score:', cv_results['l2-mean'][-1])
Temos dois algoritmos para olhar com carinho. O vencedor de competições Kaggle - lightgbm e o random forest. Vamos testá-los agora em nosso conjunto de dados.
In [55]:
### Treinar LightGBM
bst = lgb.train(params, train_data, 182)
dados['pred_lgb'] = bst.predict(dados[features_names])
### Treinar Random Forest
model_2 = regr_2.fit(train_features, train_result)
dados['pred_m2'] = regr_2.predict(train_features)
In [84]:
### Plotar scatter com previsão do algoritmo e resultado principal
dados_plot = pd.melt(dados[['variacao_preco_lag','pred_lgb','pred_m2']],
id_vars=['variacao_preco_lag'],
value_vars=['pred_lgb','pred_m2'])
sns.set(font_scale=3)
g = sns.FacetGrid(dados_plot, col="variable", height=16)
g.map(sns.regplot, 'value', 'variacao_preco_lag', scatter_kws={'alpha':0.3})
Out[84]:
In [ ]:
dados[['slug', 'variacao_preco_lag', 'predict_m2', 'pred_lbg']].tail(20)