https://github.com/johnny-godoy/laboratorios-mds/blob/main/lab%207/laboratorio_7.ipynb
¶Pipelines
.El laboratorio deberá ser desarrollado sin el uso indiscriminado de iteradores nativos de python (aka "for", "while"). La idea es que aprendan a exprimir al máximo las funciones optimizadas que nos entrega pandas
, las cuales vale mencionar, son bastante más eficientes que los iteradores nativos sobre DataFrames
.
from __future__ import annotations
# Libreria Core del lab.
import numpy as np
import pandas as pd
from sklearn.pipeline import Pipeline, make_pipeline
from sklearn.compose import ColumnTransformer
from sklearn.model_selection import train_test_split
# Pre-procesamiento
from sklearn.dummy import DummyClassifier, DummyRegressor
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import FunctionTransformer
from sklearn.preprocessing import PowerTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import OrdinalEncoder
# Metricas de evaluación
from sklearn.metrics import classification_report
from sklearn.metrics import accuracy_score
from sklearn.metrics import f1_score
from sklearn.metrics import cohen_kappa_score
# Clasificadores
from sklearn.svm import LinearSVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
# Regresores
from sklearn.linear_model import Ridge
from sklearn.ensemble import RandomForestRegressor
from sklearn.svm import SVR
#Libreria para plotear
import plotly.express as px
from plotly.subplots import make_subplots
import plotly.graph_objects as go
pd.options.plotting.backend = "plotly"
Tras el trágico despido de la mítica mascota de Maipú, Renacín decide adentrarse como consultor en el mercado futbolero, el cuál (para variar...) está cargado en especulaciones.
Como su principal tarea será asesorar a los directivos de los clubes sobre cuál jugador comprar y cuál no, Renacín desea generar modelos predictivos que evaluén distintas características de los jugadores; todo con el fin de tomar decisiones concretas basadas en los datos.
Sin embargo, su condición de corporeo le impidió tomar la versión anterior de MDS7202, por lo que este motivo Renacín contrata a su equipo para lograr su objetivo final. Dado que aún tiene fuertes vínculos con la dirección de deportes de la municipalidad, el corporeo le entrega base de datos con las estadísticas de cada jugador para que su equipo empieze a trabajar ya con un dataset listo para ser usado.
Los Datos
Para este laboratorio deberán trabajar con los csv statsplayers.csv
y salarios.pickle
, donde deberán aplicar algoritmos de de aprendizaje supervisado (clasificación y regresión) en base a características que describen de jugadores de futbol.
Para comenzar cargue el dataset señalado y a continuación vea el reporte Player_Stats_Report.html
(adjunto en la carpeta del enunciado) que describe las características principales del DataFrame
.
df_players = pd.read_csv('data/stats_players.csv')
df_players
Name | Nationality | National_Position | Club_Position | Height | Weight | Preffered_Foot | Age | Work_Rate | Weak_foot | ... | Agility | Jumping | Heading | Shot_Power | Finishing | Long_Shots | Curve | Freekick_Accuracy | Penalties | Volleys | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Cristiano Ronaldo | Portugal | LS | LW | 185 | 80 | Right | 32 | High / Low | 4 | ... | 90 | 95 | 85 | 92 | 93 | 90 | 81 | 76 | 85 | 88 |
1 | Lionel Messi | Argentina | RW | RW | 170 | 72 | Left | 29 | Medium / Medium | 4 | ... | 90 | 68 | 71 | 85 | 95 | 88 | 89 | 90 | 74 | 85 |
2 | Neymar | Brazil | LW | LW | 174 | 68 | Right | 25 | High / Medium | 5 | ... | 96 | 61 | 62 | 78 | 89 | 77 | 79 | 84 | 81 | 83 |
3 | Luis Suárez | Uruguay | LS | ST | 182 | 85 | Right | 30 | High / Medium | 4 | ... | 86 | 69 | 77 | 87 | 94 | 86 | 86 | 84 | 85 | 88 |
4 | Manuel Neuer | Germany | GK | GK | 193 | 92 | Right | 31 | Medium / Medium | 4 | ... | 52 | 78 | 25 | 25 | 13 | 16 | 14 | 11 | 47 | 11 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
17583 | Adam Dunbar | Republic of Ireland | NaN | Sub | 183 | 82 | Right | 19 | Medium / Medium | 1 | ... | 27 | 56 | 14 | 16 | 13 | 13 | 11 | 13 | 15 | 12 |
17584 | Dylan McGoey | Republic of Ireland | NaN | Sub | 185 | 80 | Right | 19 | Medium / Medium | 2 | ... | 28 | 53 | 12 | 17 | 12 | 11 | 12 | 13 | 16 | 12 |
17585 | Tommy Ouldridge | England | NaN | Res | 173 | 61 | Right | 18 | High / Medium | 2 | ... | 54 | 61 | 41 | 44 | 28 | 42 | 35 | 36 | 42 | 37 |
17586 | Mark Foden | Scotland | NaN | Sub | 180 | 80 | Right | 21 | Medium / Medium | 3 | ... | 34 | 48 | 15 | 23 | 14 | 12 | 13 | 12 | 24 | 12 |
17587 | Barry Richardson | England | NaN | Sub | 185 | 77 | Right | 47 | Medium / Medium | 2 | ... | 38 | 51 | 12 | 13 | 11 | 16 | 12 | 11 | 22 | 12 |
17588 rows × 39 columns
Como primera tarea, Renacín, intrigado por la posibilidad de saber qué tan reconocido es un jugador, le consulta a su equipo si es posible predecir si un jugador será o no seleccionado nacional a partir de sus estadísticas en el juego.
Primero comience generando las labels para la clasificación. Para esto, trabaje sobre el atributo National_Position
suponiendo que los valores nulos son jugadores no seleccionados para representar a su país.
Hecho esto, ¿Cuantos ejemplos por cada clase se tienen? Comente lo que observa.
Respuesta:
Valores nulos
print(f"Cantidad de valores nulos: {df_players.National_Position.isna().sum()}")
Cantidad de valores nulos: 16513
print("Valores por cada clase")
counts = df_players.National_Position.value_counts()[::-1]
fig = px.bar(x=counts.values, y=counts.index)
fig.update_layout(title="Cantidad de valores por cada clase",
xaxis_title="Cantidad de valores",
yaxis_title="Clase",
)
fig.show()
Valores por cada clase
Creando la etiqueta
y = ~df_players.National_Position.isna()
y.hist()
Donde observamos un fuerte desbalance de clases (1k vs 16k)
Igualmente, aprovechamos de crear la entrada.
X = df_players.drop("National_Position", axis=1)
Para preprocesar el dataset, genere un ColumnTransformer
en donde especifique las transformaciones que hay que realizar para cada columna (por ejemplo StandarScaler, MinMaxScaler, OneHotEncoder, etc...) para que puedan ser utilizadas correctamente por el modelo predictivo y guardelo en algúna variable.
Luego, comente y justifique las transformaciones elegidas sobre cada una de las variables (para esto utilice el material Player_Stats_Report.html
que viene en el zip del lab), al igual que las transformaciones aplicadas.
Hecho lo anterior, defina al menos 3 pipelines para la clasificación, en donde utilice el mismo ColumnTransformer definido anteriormente, pero que varie entre cada pipeline los clasificadores.
Para seleccionar los clasificadores más adecuados, utilice la siguiente guía:
Con ella, comente y justifique cada una de las decisiones tomadas al momento de desarrollar su pipeline.
Nota: Si tiene problemas al utilizar OneHotEncoder puede utilizar el parámetro handle_unknown='ignore'. Esto hace que en la codificación se omitan las categorias que no aparecen en el entrenamiento. Pregunta dudosa (no tiene puntaje), ¿esto tiene sentido a nivel de modelos?.
To-Do:
ColumnTransformer
enfocado en preprocesar los datos.pipelines
con diferentes clasificadores.pipelines
aún.Nota: No es necesario entrenar los clasificadores aún.
Respuesta:
exclude = {"Name", "Nationality", "Club Position", # Alta cardinalidad
"National_Position", # Variable objetivo
}
def get_col_index(columns: list[str], frame = X) -> list[int]:
"""Retorna los índices de las columnas dadas."""
return list(np.where(frame.columns.isin(columns))[0])
int_index = get_col_index(X.select_dtypes("int").columns)
float_index = get_col_index(X.select_dtypes("float").columns)
object_index = get_col_index(set(X.select_dtypes("object").columns) - exclude)
club_position_index = get_col_index(["Club_Position"])
preprocessor = ColumnTransformer([("int_processor", make_pipeline(SimpleImputer(strategy="median"), MinMaxScaler()), int_index),
("float_processor", make_pipeline(StandardScaler(), SimpleImputer(strategy="mean")), float_index),
("small_cat_processor", OneHotEncoder(handle_unknown='ignore', drop='first', sparse=False), object_index),
("large_cat_processor", make_pipeline(SimpleImputer(strategy="most_frequent"), OrdinalEncoder(), MinMaxScaler()), club_position_index),
])
linear_svc = make_pipeline(preprocessor,
LinearSVC())
knn = make_pipeline(preprocessor,
KNeighborsClassifier())
random_forest = make_pipeline(preprocessor,
RandomForestClassifier())
pipelines = (linear_svc, knn, random_forest)
El procesamiento se separa como tal:
Ahora, entrene los pipeline
generados en los pasos anteriores. Para esto, primero separe los datos de entrenamiento en un conjunto de entrenamiento y de prueba (la proporción queda a su juicio).
En este paso, seleccione los ejemplos de forma aleatoria e intente mantener la distribución original de labels de cada clase en los conjuntos de prueba/entrenamiento. (vea la documentación de train_test_split
).
Luego, entrene los pipelines
Una vez entrenado su modelo, evalue su rendimiento a través de diferentes métricas, comentando que significa cada uno de los valores obtenidos. Puede usar la función classification_report
para corroborar sus resultados.
To-Do:
pipelines
.Respuesta:
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0,
shuffle=True, stratify=y)
Agregamos la predicción Dummy para tener un entendimiento base
dummy_pred = DummyClassifier().fit(X_train, y_train).predict(X_test)
print(classification_report(y_test, dummy_pred))
precision recall f1-score support False 0.94 1.00 0.97 4128 True 0.00 0.00 0.00 269 accuracy 0.94 4397 macro avg 0.47 0.50 0.48 4397 weighted avg 0.88 0.94 0.91 4397
C:\Users\David\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\metrics\_classification.py:1327: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior. C:\Users\David\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\metrics\_classification.py:1327: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior. C:\Users\David\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\metrics\_classification.py:1327: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
Vemos que por el alto desbalance, este clasificador dice siempre Falso y obtiene 94% de precisión en esta clase. Nos interesará más que vea correctamente quienes sí fueron seleccionados.
for model in pipelines:
y_pred = model.fit(X_train, y_train).predict(X_test)
clf = [val for key, val in model.named_steps.items() if val != "pipeline"][-1]
print(f"Resultados para {clf.__class__.__name__}")
print(classification_report(y_test, y_pred))
Resultados para LinearSVC precision recall f1-score support False 0.94 1.00 0.97 4128 True 0.00 0.00 0.00 269 accuracy 0.94 4397 macro avg 0.47 0.50 0.48 4397 weighted avg 0.88 0.94 0.91 4397 Resultados para KNeighborsClassifier precision recall f1-score support False 0.94 1.00 0.97 4128 True 0.46 0.06 0.11 269 accuracy 0.94 4397 macro avg 0.70 0.53 0.54 4397 weighted avg 0.91 0.94 0.92 4397 Resultados para RandomForestClassifier precision recall f1-score support False 0.94 1.00 0.97 4128 True 0.57 0.09 0.15 269 accuracy 0.94 4397 macro avg 0.76 0.54 0.56 4397 weighted avg 0.92 0.94 0.92 4397
Respuesta:
LinearSVC tiene el peor desempeño, pues es un modelo simple que no capturó nada mejor que evaluar a todo punto como negativo.
KNN logró un mejor desempeño en clasificar efectivamente a los seleccionados sin perder desempeó para los no seleccionados. Cabe destacar que este método puede sufrir por la maldición de la dimensionalidad, por lo cual no se esperaría que fuera el mejor en esta situación.
RandomForest alivia este problema, ligeramente mejorando los resultados de KNN.
En una nueva jornada de desmesuradas transacciones deportivas, Renacín escuchó a sus colegas discutir acerca de que el precio de cada jugador depende en gran medida de la posición en la cancha en la que juega. Y además, que hay bastantes jugadores nuevos que no tienen muy claro en que posición verdaderamente brillarían, por lo que actualmente puede que actualmente estén jugando en posiciones sub-optimas.
Viendo que los resultados del primer análisis no son tan esperanzadores, el corporeo los comanda a cambiar su tarea: ahora, les solicita que construyan un clasificador enfocado en predecir la mejor posición de los jugadores en la cancha según sus características.
Para lograr esto, primero, les pide que etiqueten de la siguiente manera los valores que aparecen en el atributo Club_Position
, pidiendo que agrupen los valores en los siguientes grupos:
Nota: Renacín les recalca que no deben utilizar los valores Sub
y Res
de esta columna.
ataque = ['ST', 'CF']
central_ataque = ['RW', 'CAM', 'LW']
central = ['RM', 'CM', 'LM']
central_defensa = ['RWB', 'CDM', 'LWB']
defensa = ['RB', 'CB', 'LB']
arquero = ['GK']
Cabe señalar que al igual como lo realizado con la clasificación binaria, deberá justificar en base a la guía la elección del clasificador y se deben comentar los resultados obtenidos en la clasificación.
Nota: Clasifique solamente con las clases señaladas, si observa mas clases eliminelas de la clasficación.
To-Do:
Respuesta:
Aplicando etiquetas
replace = {"ST": "ataque",
"CF": "ataque",
"RW": "central_ataque",
"CAM": "central_ataque",
"LW": "central_ataque",
"RM": "central",
"CM": "central",
"LM": "central",
"RWB": "central_defensa",
"CDM": "central_defensa",
"LWB": "central_defensa",
"RB": "defensa",
"CB": "defensa",
"LB": "defensa",
"GK": "arquero"
}
posicion = df_players.Club_Position.replace(replace)
posicion = posicion[posicion.isin(set(replace.values()))]
X_new = X.drop("Club_Position", axis=1).iloc[posicion.index]
Contando clases
y_plot = posicion.value_counts()[::-1]
fig = px.histogram(x=y_plot.values, y=y_plot.index)
fig.update_layout(title="Cantidad de valores por cada clase",
xaxis_title="Cantidad de valores",
yaxis_title="Clase",
)
fig.show()
Se realiza el split
X_train, X_test, y_train, y_test = train_test_split(X_new, posicion, random_state=0,
shuffle=True, stratify=posicion)
Tal como antes, se probarán los clasificadores según la guía dada, pero en este caso, se probarán los clasificadores uno por uno, porque si es suficiente uno de bajo calibre, no necesitamos subirlo.
Las decisiones de transformaciones serán similares a las anteriores, con las consideraciones:
int_index = get_col_index(X_new.select_dtypes("int").columns, X_new)
float_index = get_col_index(X_new.select_dtypes("float").columns, X_new)
object_index = get_col_index(set(X_new.select_dtypes("object").columns) - exclude)
Nuevamente, miremos el Dummy
dummy_pred = DummyClassifier().fit(X_train, y_train).predict(X_test)
print(classification_report(y_test, dummy_pred))
precision recall f1-score support arquero 0.00 0.00 0.00 158 ataque 0.00 0.00 0.00 108 central 0.00 0.00 0.00 227 central_ataque 0.00 0.00 0.00 145 central_defensa 0.00 0.00 0.00 52 defensa 0.30 1.00 0.46 295 accuracy 0.30 985 macro avg 0.05 0.17 0.08 985 weighted avg 0.09 0.30 0.14 985
C:\Users\David\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\metrics\_classification.py:1327: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior. C:\Users\David\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\metrics\_classification.py:1327: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior. C:\Users\David\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\metrics\_classification.py:1327: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
Solamente predice la clase mayoritaria defensa, y por eso los pésimos resultados: Solamente 0.08 de f1-score como macro promedio.
Hay menos de 100k datos, así que empezamos por LinearSVC
svc_processor = ColumnTransformer([("int_processor", make_pipeline(SimpleImputer(strategy="median"), MinMaxScaler()), int_index),
("float_processor", make_pipeline(StandardScaler(), SimpleImputer(strategy="mean")), float_index),
("small_cat_processor", OneHotEncoder(handle_unknown='ignore', drop='first', sparse=False), object_index),
])
linear_svc = make_pipeline(svc_processor, LinearSVC())
y_pred = linear_svc.fit(X_train, y_train).predict(X_test)
print(classification_report(y_test, y_pred))
precision recall f1-score support arquero 1.00 1.00 1.00 158 ataque 0.76 0.86 0.81 108 central 0.61 0.66 0.63 227 central_ataque 0.53 0.30 0.39 145 central_defensa 0.62 0.19 0.29 52 defensa 0.80 0.98 0.88 295 accuracy 0.75 985 macro avg 0.72 0.66 0.67 985 weighted avg 0.73 0.75 0.73 985
C:\Users\David\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\preprocessing\_encoders.py:188: UserWarning: Found unknown categories in columns [0] during transform. These unknown categories will be encoded as all zeros
Este modelo logra un desempeño perfecto para los arqueros, y bastante aceptable en general, con el resultado más débil siendo un mal recall para central_defensa que es la categoría minoritaria. Con un macro promedio de 0.67 del f1-score, uno podría quedar satisfecho con esto, pero vale la pena seguir probando otros modelos. Seguimos según la guía con KNN, que puede usar el mismo procesamiento sin problema
knn = make_pipeline(svc_processor, KNeighborsClassifier())
y_pred = knn.fit(X_train, y_train).predict(X_test)
print(classification_report(y_test, y_pred))
C:\Users\David\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\preprocessing\_encoders.py:188: UserWarning: Found unknown categories in columns [0] during transform. These unknown categories will be encoded as all zeros
precision recall f1-score support arquero 1.00 1.00 1.00 158 ataque 0.65 0.73 0.69 108 central 0.50 0.56 0.52 227 central_ataque 0.41 0.24 0.30 145 central_defensa 0.13 0.04 0.06 52 defensa 0.74 0.88 0.81 295 accuracy 0.67 985 macro avg 0.57 0.57 0.56 985 weighted avg 0.64 0.67 0.65 985
En cada clase, estos resultados son peores que el modelo lineal.
Progresamos con RandomForest, que como está basado en árboles, no requiere escalamiento (mejorando interpretabilidad si se quiere), y puede lidiar con OrdinalEncoding en vez de OneHotEncoding, lo cual en ocasiones mejora el desempeño.
tree_processor = ColumnTransformer([("int_processor", SimpleImputer(strategy="median", add_indicator=True), int_index),
("float_processor", SimpleImputer(strategy="mean", add_indicator=True), float_index),
("small_cat_processor", OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1,
encoded_missing_value=-2), object_index),
])
tree = make_pipeline(tree_processor, RandomForestClassifier())
y_pred = tree.fit(X_train, y_train).predict(X_test)
print(classification_report(y_test, y_pred))
precision recall f1-score support arquero 1.00 1.00 1.00 158 ataque 0.79 0.83 0.81 108 central 0.59 0.71 0.64 227 central_ataque 0.56 0.28 0.37 145 central_defensa 0.75 0.17 0.28 52 defensa 0.80 0.97 0.88 295 accuracy 0.75 985 macro avg 0.75 0.66 0.66 985 weighted avg 0.74 0.75 0.73 985
Obtenemos resultados muy similares a LinearSVC. Si queremos mejorar los resultados, podemos seguir con la guía y utilizar:
Queriendo ahondar aún más en el mercado del balompíe, Renacin, logra obtener (de una manera no muy formal) los sueldos de múltiples futbolistas y los guarda en el archivo sueldos.csv
. Con ellos les solicita que generen un regresor que les permita predecir el sueldo de los futbolistas en base a las características de los pichichis, esto, debido a su motivación por invertir y/o realizar especulación sobre los sueldos de jugadores.
Renacin es claro señalando que deben seguir utilizando la guía y comenten cada uno de los pasos realizados, para obtener su regresión lineal. Señalándoles que no aceptara un $R^2$ inferior a 0.35 para el modelo solicitado.
Para esta parte usted tiene total libertad en la generación del regresor, la unica exigencia es que utilice un pipeline para generar la regresión y utilice la metrica $R^2$ para medir el rendimiento de esta.
To-Do:
Respuesta
La métrica $R^2$ de un modelo $f$ que está hecho para predecir $y$ se define como tal:
$$ 1 - \frac{\text{ECM}(f)}{\text{ECM}(\bar{y})} $$Donde ECM es el error cuadrático medio, y $\bar{y}$ es el predictor trivial que siempre retorna la media.
En particular, modelos buenos tienen un ECM muy pequeño, así que $R^2$ es cercano a $1$ (y este es su valor máximo, dado que el ECM es no negativo). Por otro lado, si el modelo es esencialmente el trivial, este valor se parece a 0. Modelos peores que el trivial tienen $R^2$ negativo, así que esta métrica tiene una intepretación:
X_temp = X.copy()
X_temp["Posicion"] = posicion
sueldos = pd.read_csv('data/sueldos.csv').drop("Unnamed: 0", axis=1)
df_sueldos = pd.merge(X_temp, sueldos, left_on='Name', right_on='Player')
df_sueldos
Name | Nationality | Club_Position | Height | Weight | Preffered_Foot | Age | Work_Rate | Weak_foot | Skill_Moves | ... | Shot_Power | Finishing | Long_Shots | Curve | Freekick_Accuracy | Penalties | Volleys | Posicion | Player | Weekly Salary | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Cristiano Ronaldo | Portugal | LW | 185 | 80 | Right | 32 | High / Low | 4 | 5 | ... | 92 | 93 | 90 | 81 | 76 | 85 | 88 | central_ataque | Cristiano Ronaldo | 1248536.0 |
1 | Lionel Messi | Argentina | RW | 170 | 72 | Left | 29 | Medium / Medium | 4 | 4 | ... | 85 | 95 | 88 | 89 | 90 | 74 | 85 | central_ataque | Lionel Messi | 1538905.0 |
2 | Neymar | Brazil | LW | 174 | 68 | Right | 25 | High / Medium | 5 | 5 | ... | 78 | 89 | 77 | 79 | 84 | 81 | 83 | central_ataque | Neymar | 797726.0 |
3 | Luis Suárez | Uruguay | ST | 182 | 85 | Right | 30 | High / Medium | 4 | 4 | ... | 87 | 94 | 86 | 86 | 84 | 85 | 88 | ataque | Luis Suárez | 508923.0 |
4 | Manuel Neuer | Germany | GK | 193 | 92 | Right | 31 | Medium / Medium | 4 | 1 | ... | 25 | 13 | 16 | 14 | 11 | 47 | 11 | arquero | Manuel Neuer | 326233.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
1861 | Phillip Menzel | Germany | Res | 191 | 83 | Right | 18 | Medium / Medium | 3 | 1 | ... | 25 | 5 | 10 | 12 | 8 | 15 | 5 | NaN | Phillip Menzel | 2034.0 |
1862 | Manuel Akanji | Switzerland | Sub | 187 | 85 | Right | 21 | Medium / Medium | 2 | 2 | ... | 38 | 25 | 18 | 26 | 27 | 40 | 31 | NaN | Manuel Akanji | 54176.0 |
1863 | Moritz Nicolas | Germany | Res | 195 | 87 | Right | 19 | Medium / Medium | 2 | 1 | ... | 22 | 9 | 10 | 17 | 14 | 17 | 11 | NaN | Moritz Nicolas | 2262.0 |
1864 | Giacomo Satalino | Italy | Sub | 188 | 70 | Right | 17 | Medium / Medium | 1 | 1 | ... | 19 | 6 | 5 | 13 | 11 | 18 | 9 | NaN | Giacomo Satalino | 2827.0 |
1865 | Nicolò Zaniolo | Italy | Res | 185 | 80 | Right | 17 | High / Low | 3 | 2 | ... | 50 | 49 | 42 | 54 | 51 | 49 | 39 | NaN | Nicolò Zaniolo | 28187.0 |
1866 rows × 41 columns
X_reg = df_sueldos.drop(["Weekly Salary", "Name", "Player", "Nationality"], axis=1)
y_reg = df_sueldos["Weekly Salary"]
X_train, X_test, y_train, y_test = train_test_split(X_reg, y_reg, random_state=0, shuffle=True)
Vamos a seguir la guía tal como antes, empezando al notar que siguen habiendo menos de 100k muestras. No priorizaremos que pocas características sean importantes, así que empezaremos con los dos recomendados: RidgeRegression y SVR lineal. Por simplicidad, utilizaremos siempre el mismo pipeline para todo, muy similar a los anteriores. Notemos que los regresores de sklearn implementan $R^2$ en su método score.
int_index = get_col_index(X_reg.select_dtypes("int").columns, X_reg)
float_index = get_col_index(X_reg.select_dtypes("float").columns, X_reg)
object_index = get_col_index(set(X_reg.select_dtypes("object").columns) - exclude)
reg_processor = ColumnTransformer([("int_processor", make_pipeline(SimpleImputer(strategy="median"), MinMaxScaler()), int_index),
("float_processor", make_pipeline(StandardScaler(), SimpleImputer(strategy="mean")), float_index),
("small_cat_processor", OneHotEncoder(handle_unknown='ignore', drop='first', sparse=False), object_index),
])
models = (Ridge(), SVR(kernel="linear"))
for model in models:
pipeline = make_pipeline(reg_processor, model)
score = pipeline.fit(X_train, y_train).score(X_test, y_test)
print(f"R^2 para {model.__class__.__name__}: {score}")
R^2 para Ridge: 0.2507424762140851
C:\Users\David\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\preprocessing\_encoders.py:188: UserWarning: Found unknown categories in columns [0] during transform. These unknown categories will be encoded as all zeros C:\Users\David\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\preprocessing\_encoders.py:188: UserWarning: Found unknown categories in columns [0] during transform. These unknown categories will be encoded as all zeros
R^2 para SVR: -0.1115151394538163
Ninguno de estos dos cumple el requisito de $R^2$, con SVR peor que el modelo dummy, por lo cual avanzamos a Random Forest y kernel rbf.
models = (RandomForestRegressor(), SVR(kernel="rbf"))
for model in models:
pipeline = make_pipeline(reg_processor, model)
score = pipeline.fit(X_train, y_train).score(X_test, y_test)
print(f"R^2 para {model.__class__.__name__}: {score}")
C:\Users\David\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\preprocessing\_encoders.py:188: UserWarning: Found unknown categories in columns [0] during transform. These unknown categories will be encoded as all zeros
R^2 para RandomForestRegressor: 0.5617814005119344 R^2 para SVR: -0.11187892479809713
C:\Users\David\AppData\Local\Programs\Python\Python310\lib\site-packages\sklearn\preprocessing\_encoders.py:188: UserWarning: Found unknown categories in columns [0] during transform. These unknown categories will be encoded as all zeros
SVR sigue con un pésimo resultado (peor que dummy), pero RF logra $R^2$ de 0.6, muy satisfactorio dado el objetivo planteado de $0.35$, por lo cual ya no se considerará necesario mejorar los modelos ni agregar datos/características.
Eso ha sido todo para el lab de hoy, recuerden que el laboratorio tiene un plazo de entrega de una semana. Cualquier duda del laboratorio, no duden en contactarnos por mail o U-cursos.