Predicción de remuneración de futbolistas

Introducción

Acontinuación se realizará una predicción de remuneraciones de futbolistas a partir de estadísticas sobre sus habilidades de juego. Para ellos se filtrarán los datos de habilidades y se realizará un análisis exploratorio para identificar patrones en los datos que permitan tener información fiable e implementar una predicción mediante una regresión lineal en tensorflow.

Análisis exploratorio.

Contexto

Los datos son estadísticas del juego Fifa 19 con los principales equipos y jugadores de fútbol del mundo. La base de datos se encuentran en https://www.kaggle.com/karangadiya/fifa19.

Estos datos fueron extraídos desde https://sofifa.com/.

Para este análisis se utilizarán sólo las variables relacionadas a habilidades específicas de los jugadores, esta son:

LS (Left Striker): Habilidad en la posición (0-99).
ST (Striker): Habilidad en la posición (0-99).
RS (Right Striker): Habilidad en la posición (0-99).
LW (Left Wing): Habilidad en la posición (0-99).
LF (Left Forward): Habilidad en la posición (0-99).
CF (Center Forward): Habilidad en la posición (0-99).
RF (Right Forward): Habilidad en la posición (0-99).
RW (Right Wing): Habilidad en la posición (0-99).
LAM (Left Attacking Midfielder): Habilidad en la posición (0-99).
CAM (Center Attacking Midfielder): Habilidad en la posición (0-99).
RAM (Right Attacking Midfielder): Habilidad en la posición (0-99).
LM (Left Midfielder): Habilidad en la posición (0-99).
LCM (Left Center Midfielder): Habilidad en la posición (0-99).
CM (Center Midfielder): Habilidad en la posición (0-99).
RCM (Right Center Midfielder): Habilidad en la posición (0-99).
RM (Right Midfielder): Habilidad en la posición (0-99).
LWB (Lef Wing Back): Habilidad en la posición (0-99).
LDM (Left Defensive Midfielder): Habilidad en la posición (0-99).
CDM (Center Defensive Midfielder): Habilidad en la posición (0-99).
RDM (Right Defensive Midfielder): Habilidad en la posición (0-99).
RWB (Right Wing Back): Habilidad en la posición (0-99).
LB (Lef Back): Habilidad en la posición (0-99).
LCB (Left Center Back): Habilidad en la posición (0-99).
CB (Center Back): Habilidad en la posición (0-99).
RCB (Right Center Back): Habilidad en la posición (0-99).
RB (Right Back): Habilidad en la posición (0-99).
Crossing: Ofensiva, habilidad en centros (0-99).
Finishing: Ofensiva, habilidad en definición (0-99).
HeadingAccuracy: Ofensiva, precisión con la cabeza (0-99).
ShortPassing: Ofensiva, habilidad en pases cortos (0-99).
Volleys: Ofensiva, habilidad en voleas (0-99).
Dribbling: Técnica, habilidad en regates (0-99).
Curve: Técnica, habilidad en balones con efecto (0-99).
FKAccuracy: Técnica, habilidad en precisión faltas (0-99).
LongPassing: Técnica, habilidad en pases largos (0-99).
BallControl: Técnica, habilidad en control del balón (0-99).
Acceleration: Movimiento, aceleración (0-99).
SprintSpeed: Movimiento, velocidad (0-99).
Agility: Movimiento, agilidad (0-99).
Reactions: Movimiento, reflejos (0-99).
Balance: Movimiento, equilibrio (0-99).
ShotPower: Potencia, potencia (0-99).
Jumping: Potencia, salto (0-99).
Stamina: Potencia, resistencia (0-99).
Strength: Potencia, fuerza (0-99).
LongShots: Potencia, tiros lejanos (0-99).
Aggression: Mentalidad, agresividad (0-99).
Interceptions: Mentalidad, intercepción (0-99).
Positioning: Mentalidad, colocación (0-99).
Vision: Mentalidad, visión (0-99).
Penalties: Mentalidad, penales (0-99).
Composure: Mentalidad, compostura (0-99).
Marking: Defensa, marcaje (0-99)
StandingTackle: Defensa, robos (0-99)
SlidingTackle: Defensa, entrada agresiva (0-99)
GKDiving: Portero, estirada (0-99)
GKHandling: Portero, paradas (0-99)
GKKicking: Portero, saques (0-99)
GKPositioning: Portero, colocación (0-99)
GKReflexes: Portero, reflejos (0-99)

La variable a predecir es Wage que es el salario del jugador.

Además, se dejará la variable Name (nombre del jugador) como marcador para contextualizar el resultado.

Análisis de los datos

In [1]:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tensorflow as tf
from sklearn.preprocessing import normalize
from sklearn import linear_model
import re
import seaborn as sns; sns.set(style="ticks", color_codes=True)
In [2]:
# Filtrar columnas a utilizar.
df=pd.read_csv('data.csv')
header = list(df)
Name = df[[list(df)[2]]]
x_vals = df[list(df)[28:(df.shape[1]-1)]]
header = list(x_vals)
y_vals = df[[list(df)[12]]]
In [3]:
Name.head(5)
Out[3]:
Name
0 L. Messi
1 Cristiano Ronaldo
2 Neymar Jr
3 De Gea
4 K. De Bruyne

La variable Name no requiere transformación.

La variable Wage a predecir está en formato de texto y con una nomenclatura que no sirve para realizar cálculos, se debe transformar.

In [4]:
y_vals.head(10)
pd.unique(y_vals['Wage'])
Out[4]:
array(['€565K', '€405K', '€290K', '€260K', '€355K', '€340K', '€420K',
       '€455K', '€380K', '€94K', '€205K', '€125K', '€285K', '€225K',
       '€145K', '€240K', '€315K', '€200K', '€130K', '€300K', '€215K',
       '€100K', '€255K', '€165K', '€265K', '€160K', '€150K', '€245K',
       '€110K', '€77K', '€115K', '€210K', '€195K', '€230K', '€250K',
       '€135K', '€155K', '€180K', '€175K', '€190K', '€185K', '€21K',
       '€82K', '€73K', '€92K', '€88K', '€96K', '€170K', '€66K', '€235K',
       '€28K', '€105K', '€38K', '€81K', '€57K', '€15K', '€63K', '€22K',
       '€84K', '€120K', '€90K', '€72K', '€93K', '€45K', '€74K', '€51K',
       '€42K', '€31K', '€75K', '€25K', '€140K', '€41K', '€78K', '€53K',
       '€95K', '€80K', '€43K', '€60K', '€85K', '€64K', '€67K', '€18K',
       '€70K', '€91K', '€20K', '€49K', '€87K', '€86K', '€26K', '€29K',
       '€55K', '€35K', '€33K', '€56K', '€30K', '€11K', '€59K', '€23K',
       '€46K', '€39K', '€32K', '€36K', '€98K', '€54K', '€68K', '€58K',
       '€27K', '€40K', '€44K', '€19K', '€1K', '€61K', '€50K', '€99K',
       '€17K', '€52K', '€62K', '€12K', '€10K', '€71K', '€14K', '€76K',
       '€48K', '€65K', '€69K', '€24K', '€34K', '€16K', '€37K', '€47K',
       '€89K', '€0', '€97K', '€79K', '€13K', '€83K', '€6K', '€3K', '€9K',
       '€8K', '€7K', '€4K', '€2K', '€5K'], dtype=object)
In [5]:
y_vals = np.array([float(re.sub(r'K$' ,"", re.sub(r'^€', "", x)))*1000 for x in  y_vals['Wage']])
y_vals
Out[5]:
array([565000., 405000., 290000., ...,   1000.,   1000.,   1000.])

Una vez transformada a float la variable a predecir (Wage), se revisará la distribución de los datos.

Observando el histograma claramente hay pocos jugadores que reciben una remuneración muy alta, esto puede causar malas predicciones. Para ello vamos a considerar en las predicciones el log(Wage).

Por otro lado el histograma del Wage muestra algunas observaciones con valor 0, estos son valores atípicos y no serán considerado e el análisis.

In [6]:
Wage_no0 = np.where(y_vals>0)[0]
y_vals = y_vals[Wage_no0]
x_vals = np.array(x_vals)[Wage_no0,:]
Name = np.array(Name)[Wage_no0]

flierprop = dict(markerfacecolor='None', marker='o', markeredgecolor='xkcd:violet')
medianprop =  dict(color='xkcd:salmon')

fig, axs = plt.subplots(1,4, sharex=False, figsize=[14,3])
fig.suptitle('Histograma y Boxplot de remuneraciones de futbolistas',
             y=0.99, size=15)
fig.subplots_adjust(hspace=0.5, wspace=0.25, top=0.8)
axs[0].hist(y_vals, color='xkcd:violet')
axs[0].set_title('Wage')
axs[1].hist(np.log(y_vals+1), color='xkcd:violet')
axs[1].set_title('Log(Wage)')        
axs[2].boxplot(y_vals, vert=False, flierprops=flierprop, medianprops=medianprop)
axs[2].set_title('Wage') 
axs[3].boxplot(np.log(y_vals+1), vert=False, flierprops=flierprop, medianprops=medianprop)
axs[3].set_title('Log(Wage)') 
Out[6]:
Text(0.5, 1.0, 'Log(Wage)')

Hay mucho valores atípicos superiores, al examinar estos valores y evaluarlos se observa que el conjunto de valores atípicos tiene valores atípicos (o sea, algunos futbolistas ganan sumas muy muy altas comparados con los futbolistas que ya ganan sumas altas).

Por lo que para efectos prácticos se dividirá el conjunto en 3 grupos sueldos normales, sueldos altos y sueldos muy altos y se utilizará el logaritmo del sueldo como dato de remuneración.

In [7]:
corte = 1.5

Q1 = pd.DataFrame(y_vals).quantile(0.25)[0]
Q3 = pd.DataFrame(y_vals).quantile(0.75)[0]
IQR = Q3 - Q1

y_vals_sa = y_vals[y_vals>=(Q3 + corte * IQR)]
Qa1 = pd.DataFrame(y_vals_sa).quantile(0.25)[0]
Qa3 = pd.DataFrame(y_vals_sa).quantile(0.75)[0]
IQRa = Qa3 - Qa1

Sueldo_normal = y_vals<(Q3 + corte * IQR) 
Sueldo_alto = (y_vals>=(Q3 + corte * IQR))&(y_vals<(Qa3 + corte * IQRa) )
Sueldo_muyalto = y_vals>=(Qa3 + corte * IQRa)

print('Hay '+str(sum(Sueldo_normal))+' futbolistas con sueldo normal.')
print('Hay '+str(sum(Sueldo_alto))+' futbolistas con sueldo alto.')
print('Hay '+str(sum(Sueldo_muyalto))+' futbolistas con sueldo muy alto.')
Hay 15824 futbolistas con sueldo normal.
Hay 1929 futbolistas con sueldo alto.
Hay 213 futbolistas con sueldo muy alto.
In [8]:
flierprop = dict(markerfacecolor='None', marker='o', markeredgecolor='xkcd:violet')
medianprop =  dict(color='xkcd:salmon')

fig, axs = plt.subplots(1,4, sharex=False, figsize=[14,3])
fig.suptitle('Histograma y Boxplot de remuneraciones de futbolistas (Sueldos Normales)',
             y=0.99, size=15)
fig.subplots_adjust(hspace=0.5, wspace=0.25, top=0.8)
axs[0].hist(y_vals[Sueldo_normal], color='xkcd:violet')
axs[0].set_title('Wage')
axs[1].hist(np.log(y_vals[Sueldo_normal]+1), color='xkcd:violet')
axs[1].set_title('Log(Wage)')        
axs[2].boxplot(y_vals[Sueldo_normal], vert=False, flierprops=flierprop, medianprops=medianprop)
axs[2].set_title('Wage') 
axs[3].boxplot(np.log(y_vals[Sueldo_normal]+1), vert=False, flierprops=flierprop, medianprops=medianprop)
axs[3].set_title('Log(Wage)') 

    
fig, axs = plt.subplots(1,4, sharex=False, figsize=[14,3])
fig.suptitle('Histograma y Boxplot de remuneraciones de futbolistas (Sueldos Altos)',
             y=0.99, size=15)
fig.subplots_adjust(hspace=0.5, wspace=0.25, top=0.8)
axs[0].hist(y_vals[Sueldo_alto], color='xkcd:violet')
axs[0].set_title('Wage')
axs[1].hist(np.log(y_vals[Sueldo_alto]+1), color='xkcd:violet')
axs[1].set_title('Log(Wage)')        
axs[2].boxplot(y_vals[Sueldo_alto], vert=False, flierprops=flierprop, medianprops=medianprop)
axs[2].set_title('Wage') 
axs[3].boxplot(np.log(y_vals[Sueldo_alto]+1), vert=False, flierprops=flierprop, medianprops=medianprop)
axs[3].set_title('Log(Wage)') 
    

fig, axs = plt.subplots(1,4, sharex=False, figsize=[14,3])
fig.suptitle('Histograma y Boxplot de remuneraciones de futbolistas (Sueldos Muy Altos)',
             y=0.99, size=15)
fig.subplots_adjust(hspace=0.5, wspace=0.25, top=0.8)
axs[0].hist(y_vals[Sueldo_muyalto], color='xkcd:violet')
axs[0].set_title('Wage')
axs[1].hist(np.log(y_vals[Sueldo_muyalto]+1), color='xkcd:violet')
axs[1].set_title('Log(Wage)')        
axs[2].boxplot(y_vals[Sueldo_muyalto], vert=False, flierprops=flierprop, medianprops=medianprop)
axs[2].set_title('Wage') 
axs[3].boxplot(np.log(y_vals[Sueldo_muyalto]+1), vert=False, flierprops=flierprop, medianprops=medianprop)
axs[3].set_title('Log(Wage)') 
Out[8]:
Text(0.5, 1.0, 'Log(Wage)')
In [9]:
y_vals = np.log(y_vals + 1)[Sueldo_normal]
x_vals = np.array(x_vals)[Sueldo_normal,:]
Name = np.array(Name)[Sueldo_normal]

Para los análisis posteriores y las predicciones se considera sólo los jugadores con sueldos en un rango de € 0 a € 20.000.

Ahora revisaremos los atributos de los futbolistas, hay varios NaN en el dataset, estos datos son información referida a los porteros.

Los porteros no tienen estadísticas asociadas a las posiciones definidas en el dataset, además su comportamiento a nivel de sueldos en el mercado del futbol es diferente al resto de los jugadores, por lo que para este análisis se eliminarán los datos NaN y las variables asociadas a los porteros GKDiving, GKDiving, GKKicking, GKPositioning y GKReflexes.

In [10]:
Name = [x[-1] for x in Name]
x_vals = pd.DataFrame(x_vals, columns=header)

index_null1 = np.where(x_vals[list(x_vals)[0]].isnull())
index_null2 = np.where(x_vals[list(x_vals)[26]].isnull())

index =[]
for i in range(len(index_null1[0])):
    index.append(index_null1[0][i])
for i in range(len(index_null2[0])):
    index.append(index_null2[0][i])
    
index_null = pd.unique(index)

print("Hay "+str(len(index_null))+" observaciones con valores NaN, relativos a los porteros.")
Hay 1888 observaciones con valores NaN, relativos a los porteros.
In [11]:
Name = pd.DataFrame(Name, columns=['Name'])

y_vals2 = pd.DataFrame(y_vals).drop(index_null, axis = 0) # Elimina target de las observaciones NaN.
y_vals = np.array([x for x in  y_vals2[0]])
Name = np.array(pd.DataFrame(Name).drop(index_null, axis = 0)) # Elimina observaciones NaN
x_vals = x_vals.drop(index_null, axis = 0) # Elimina observaciones NaN
In [12]:
xvals2 = np.array(x_vals)
for i in range(x_vals.shape[0]):
    for j in range(0,26):
        xvals2[i,j] = float(re.sub(r'\+\w$', "", x_vals.iloc[i,j]))
        
x_vals = xvals2[:,:55] # elimina variables asociadas a los porteros
header = header[0:55]  # elimina variables asociadas a los porteros

Ahora transformamos los datos de las variables predictoras en x_vals. Los datos de las columnas LS a la RB, que representan la puntuación de un jugador en esa posición tienen la forma 88+2, donde el primer número es el puntaje al inicio de la temporada y el segundo representa el incremento durante la temporada. En el análisis se omitirá esto último, quedandonos con el puntaje inicial.

Acontinuación se procede a dividor los conjuntos de datos en conjunto de entrenamiento(80%) y conjunto de prueba(20%).

Además se normalizarán las variables predictoras, como el rango de valoración va de 0-100, se normalizará todo por 100, quedando todas las variables predictoras entre 0-1.

In [13]:
f_train_test = 0.8
np.random.seed(123)
train_idx = np.random.choice(len(x_vals), round(len(x_vals)*f_train_test), replace=False)
test_idx = np.array(list(set(range(len(x_vals)))-set(train_idx)))
In [14]:
# Sueldo Normal
x_vals_train = x_vals[train_idx,]
x_vals_test = x_vals[test_idx]
y_vals_train = y_vals[train_idx]
y_vals_test = y_vals[test_idx]
Name_train = Name[train_idx]
Name_test = Name[test_idx]
In [15]:
# Como el rango de todas las columnas es de 0-100, dividiremos el conjunto entre 100.
x_vals_train = x_vals_train/100
x_vals_test = x_vals_test/100

Para los siguientes análisis se utilizará el conjunto de entrenamiento.

En la gráfica de correlación se observan muchas variables con alta correlación, esto puede traer inconvenientes al momento de la predicción, lo que se intentará manejar con los hiperparámetros de la norma L1 y L2.

In [16]:
f,ax = plt.subplots(figsize=(25, 15))
corr = abs(pd.DataFrame(np.array(x_vals_train, dtype='float'), columns=header).corr())
sns.heatmap(corr, annot=True, linewidths=0.5,linecolor="xkcd:gray", fmt= '.1f',ax=ax, 
            cmap='RdPu')
plt.show()
In [17]:
rows = 11
cols = 5
fig, axs = plt.subplots(rows,cols, sharex=False, figsize=[15,50])
fig.suptitle('Remuneración vs Habilidades', y=0.95, size=15)
fig.subplots_adjust(hspace=0.3, wspace=0.4, top=0.94)

nnn=0
for n1 in range(rows):
    for n2 in range(cols):
        sns.kdeplot(x_vals_train[:,nnn], y_vals_train, cmap='Reds', shade=True, shade_lowest=True, ax=axs[n1, n2])
        axs[n1, n2].set_title(header[nnn])
        axs[n1, n2].set_xlim(0.0, 1.0)
        nnn = nnn + 1

Los datos visualizados muestran una concentración de datos en valores de bajas remuneraciones, esto podría producir una mala predicción para valores altos.

Por otro lado, algunas variables muestran una leve tendencia a mostrar un alza en remuneración cuando sube la puntuación, lo que es una buena señal de predicción para una regresión lineal.

Regresión Lineal

In [18]:
# Declaración de Sesion y fijado de semillas aleatorias.
session = tf.Session()
tf.set_random_seed(321)
np.random.seed(321)

# Hiperparámetros
epochs = 1000
batch_size = int(np.round(x_vals_train.shape[0]*0.7)) # 70% del conjunto de entrenamiento
learning_rate = 0.001
elastic_param1 = tf.constant(0.05)
elastic_param2 = tf.constant(0.001)

# Declaración de placeholders
x_data = tf.placeholder(shape=[None, x_vals_train.shape[1]], dtype = tf.float32)
y_target = tf.placeholder(shape = [None, 1], dtype = tf.float32)

# Declaración de variables
A_ml = tf.Variable(tf.random_normal(shape = [x_vals_train.shape[1],1]))
b_ml = tf.Variable(tf.random_normal(shape = [1,1]))

# Declaración de predicción
y_pred = tf.add(tf.matmul(x_data, A_ml), b_ml)
WARNING:tensorflow:From C:\Users\HP\Anaconda3\lib\site-packages\tensorflow\python\framework\op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.
Instructions for updating:
Colocations handled automatically by placer.
In [19]:
# Función de pérdidas
l1_a_loss = tf.reduce_mean(tf.abs(A_ml))
l2_a_loss = tf.reduce_mean(tf.square(A_ml))
e1_term = tf.multiply(elastic_param1, l1_a_loss)
e2_term = tf.multiply(elastic_param2, l2_a_loss)
loss = tf.expand_dims(tf.add(tf.add(tf.reduce_mean(tf.square(y_target- y_pred)),e1_term), e2_term), 0)

# Inicialización de varibles
init = tf.global_variables_initializer()
session.run(init)

# Optimizador
my_opt = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
train_step = my_opt.minimize(loss)
In [20]:
loss_vec_train = []
loss_vec_test = []
loss_vec_val = []
RMSE_rl = []

for i in range(epochs):
    # carga las observaciones aleatorias de entrenamiento y pruebas
    rand_idx = np.random.choice(len(x_vals_train), size=batch_size)
    rand_test_idx = np.array(list(set(range(len(x_vals_train)))-set(rand_idx)))
    
    # separa conjunto de prueba aleatorio
    rand_x = x_vals_train[rand_idx]
    rand_y = np.transpose([y_vals_train[rand_idx]])
    
    # separa conjunto de prueba aleatorio
    rand_test_x = x_vals_train[rand_test_idx]
    rand_test_y = np.transpose([y_vals_train[rand_test_idx]])
    
    # entrenamiento iteracion i
    session.run(train_step, feed_dict={x_data : rand_x, y_target: rand_y})
    
    # perdidas del conjunto de entrenamiento
    temp_loss_train = session.run(loss, feed_dict={x_data: rand_x, y_target: rand_y})[0]
    loss_vec_train.append(temp_loss_train)

    # perdidas del conjunto de prueba
    temp_loss_test = session.run(loss, feed_dict={x_data: rand_test_x, y_target: rand_test_y})[0]
    loss_vec_test.append(temp_loss_test)
    
    # perdidas del conjunto de validación   
    temp_loss_val = session.run(loss, feed_dict={x_data: x_vals_test, y_target: np.transpose([y_vals_test])})[0]
    loss_vec_val.append(temp_loss_val)
    
    # RMSE
    pred_test_rl = session.run(y_pred, feed_dict={x_data: x_vals_test})[0]
    rmse_temp_rl = np.sqrt(np.mean(np.square(np.exp(y_vals_test) - np.exp(pred_test_rl))))
    RMSE_rl.append(rmse_temp_rl)
    
    if(i+1)%np.round(epochs/30)==0:
        acc_and_loss = [i+1, temp_loss_train, temp_loss_test, temp_loss_val, rmse_temp_rl]
        print("Paso #{}, Train Loss {:.3f}, Test Loss {:.3f}, Valid Loss {:.3f},  RMSE {:.3f}".format(*acc_and_loss))
        
Results = pd.DataFrame(Name_test, columns=['Name'])
Results['Wage'] = pd.Series(np.exp(y_vals_test)-1, index=Results.index)
pred_test_rl = session.run(y_pred, feed_dict={x_data: x_vals_test})
Results['Prediccion'] = pd.Series([np.round(np.exp(x[-1])-1,0) for x in pred_test_rl], index=Results.index)
Paso #33, Train Loss 2.051, Test Loss 2.086, Valid Loss 2.001,  RMSE 4823.845
Paso #66, Train Loss 1.898, Test Loss 1.853, Valid Loss 1.798,  RMSE 4655.989
Paso #99, Train Loss 1.868, Test Loss 1.821, Valid Loss 1.770,  RMSE 4669.678
Paso #132, Train Loss 1.829, Test Loss 1.786, Valid Loss 1.755,  RMSE 4677.616
Paso #165, Train Loss 1.739, Test Loss 1.855, Valid Loss 1.742,  RMSE 4677.164
Paso #198, Train Loss 1.783, Test Loss 1.800, Valid Loss 1.729,  RMSE 4675.203
Paso #231, Train Loss 1.777, Test Loss 1.777, Valid Loss 1.716,  RMSE 4671.922
Paso #264, Train Loss 1.765, Test Loss 1.734, Valid Loss 1.703,  RMSE 4669.724
Paso #297, Train Loss 1.752, Test Loss 1.742, Valid Loss 1.691,  RMSE 4666.919
Paso #330, Train Loss 1.734, Test Loss 1.754, Valid Loss 1.679,  RMSE 4664.202
Paso #363, Train Loss 1.719, Test Loss 1.732, Valid Loss 1.667,  RMSE 4662.054
Paso #396, Train Loss 1.725, Test Loss 1.739, Valid Loss 1.656,  RMSE 4660.522
Paso #429, Train Loss 1.688, Test Loss 1.733, Valid Loss 1.645,  RMSE 4659.443
Paso #462, Train Loss 1.680, Test Loss 1.683, Valid Loss 1.634,  RMSE 4657.925
Paso #495, Train Loss 1.685, Test Loss 1.669, Valid Loss 1.623,  RMSE 4657.500
Paso #528, Train Loss 1.622, Test Loss 1.692, Valid Loss 1.613,  RMSE 4656.608
Paso #561, Train Loss 1.628, Test Loss 1.662, Valid Loss 1.603,  RMSE 4656.340
Paso #594, Train Loss 1.623, Test Loss 1.652, Valid Loss 1.592,  RMSE 4655.966
Paso #627, Train Loss 1.572, Test Loss 1.688, Valid Loss 1.583,  RMSE 4655.919
Paso #660, Train Loss 1.637, Test Loss 1.615, Valid Loss 1.573,  RMSE 4656.091
Paso #693, Train Loss 1.630, Test Loss 1.601, Valid Loss 1.564,  RMSE 4656.277
Paso #726, Train Loss 1.567, Test Loss 1.621, Valid Loss 1.554,  RMSE 4656.512
Paso #759, Train Loss 1.584, Test Loss 1.596, Valid Loss 1.545,  RMSE 4657.160
Paso #792, Train Loss 1.573, Test Loss 1.601, Valid Loss 1.536,  RMSE 4657.381
Paso #825, Train Loss 1.568, Test Loss 1.580, Valid Loss 1.528,  RMSE 4658.486
Paso #858, Train Loss 1.567, Test Loss 1.576, Valid Loss 1.519,  RMSE 4659.562
Paso #891, Train Loss 1.524, Test Loss 1.574, Valid Loss 1.511,  RMSE 4660.845
Paso #924, Train Loss 1.539, Test Loss 1.555, Valid Loss 1.502,  RMSE 4661.224
Paso #957, Train Loss 1.558, Test Loss 1.525, Valid Loss 1.494,  RMSE 4661.567
Paso #990, Train Loss 1.547, Test Loss 1.519, Valid Loss 1.486,  RMSE 4662.630
In [21]:
plt.figure(figsize=(14,6))
plt.plot(loss_vec_train, color='xkcd:aquamarine', linestyle='-.', label="Pérdidas en Entrenamiento")
plt.plot(loss_vec_test, color='xkcd:salmon', linestyle='--', label="Pérdidas en Pruebas")
plt.plot(loss_vec_val, color='xkcd:violet', linestyle='-', label="Pérdidas en Validación")
plt.title("Pérdidas Regresión Lineal (ElasticNet)")
plt.xlabel("Iteración")
plt.ylabel("Pérdida")
plt.legend(loc="upper right")
plt.show()
In [22]:
plt.figure(figsize=(14,6))
plt.plot(RMSE_rl, color='xkcd:salmon', linestyle='-.',label="Error de Validación")
plt.axhline(y=RMSE_rl[len(RMSE_rl)-1], color='xkcd:violet', linestyle='-', 
            label = "RMSE "+str(np.round(RMSE_rl[len(RMSE_rl)-1],3)))
plt.title("Error Remuneración Futbolistas (Regresión Lineal)")
plt.xlabel("Jugadores")
plt.ylabel("RMSE")
plt.legend(loc="upper right")
plt.show()
RMSE = []
RMSE.append(RMSE_rl[len(RMSE_rl)-1])
session.close()

Resultados

La regresión lineal entrega un RMSE de 4663, que representa un 25% aproximadamente del rango máximo de los datos. En general el error es debido a que el modelo no predice bien los valores altos.

Por otro lado este resultado es mejorable considerando otros atributos como el equipo del jugador, la liga, las ventas en publicidad, el prestigio, el país, etc.

Claramente el sueldo no es sólo definido por las habilidades de un jugador (como se puede ver en la tabla inferior), hay muchas otras carácterísticas que influyen. Introduciendo estos atributos al modelo debería mejorar, sobre todo para valores altos, donde ambos modelos muestran un bajo desempeño.

In [23]:
Results.loc[np.random.choice(len(x_vals_test), size=20)]
Out[23]:
Name Wage Prediccion
1117 Park Hyeong Jin 2000.0 4815.0
1768 S. Grønning 1000.0 235.0
2685 D. Diagné 3000.0 5660.0
1849 S. Janssen 1000.0 3993.0
1187 C. Wondolowski 4000.0 5159.0
2508 E. Castillo 6000.0 3207.0
2587 Ding Haifeng 4000.0 6210.0
2325 David Haro 2000.0 1540.0
36 Escudero 20000.0 45746.0
366 R. Morioka 16000.0 7435.0
281 K. Kamara 9000.0 1220.0
173 R. Amalfitano 15000.0 23372.0
1023 O. Leguizamón 3000.0 995.0
846 G. Tzavellas 9000.0 25626.0
2754 A. Bernede 6000.0 7910.0
229 R. Vargas 2000.0 1615.0
311 D. Amoo 3000.0 2141.0
1952 M. Kaastrup 1000.0 183.0
1547 G. Kelly 1000.0 290.0
789 E. Başsan 10000.0 2360.0