Saltar al contenido principal

Tutorial de Scikit-Learn: analítica de béisbol (parte 2)

A Scikit-Learn tutorial to using logistic regression and random forest models to predict which baseball players will be voted into the Hall of Fame
· 15 min leer

Explora con IA

En la parte I de este tutorial, el objetivo fue estimar el número de partidos que ganó un equipo de la Major League Baseball (MLB) en una temporada, a partir de sus estadísticas y otras variables de esa campaña.

En la segunda parte del proyecto, probarás un modelo de regresión logística y uno de random forest de Scikit-Learn (sklearn) para predecir qué jugadores serán elegidos para el Salón de la Fama a partir de sus estadísticas y premios de carrera. Cada fila representará a un jugador y datos de su trayectoria. En béisbol hay cuatro grandes subgrupos por posición: infielders, outfielders, pitchers y catchers. Aquí nos centraremos en infielders y outfielders.

baseball

El National Baseball Hall of Fame and Museum está en Cooperstown (Nueva York) y se inauguró en 1939. Un jugador puede ser elegido para el Salón de la Fama si cumple estos criterios:

  • Haber disputado al menos diez temporadas;
  • Llevar retirado al menos cinco temporadas;
  • Un comité de evaluación debe aprobar su inclusión en la papeleta; la mayoría de quienes jugaron regularmente diez o más años se consideran dignos;
  • No estar en la lista de inelegibles (es decir, no estar expulsado del béisbol);
  • Se considera elegido si recibe al menos el 75% de los votos;
  • Permanece en la papeleta al año siguiente si recibe al menos el 5% de los votos y puede estar un máximo de 10 años.

Un jugador no elegido puede entrar por el Veterans Committee o por un comité especial designado por el comisionado de la MLB, pero en este proyecto nos centraremos solo en quienes fueron elegidos por votación. En esencia, queremos saber:

«¿Puedes construir un modelo de machine learning que prediga con precisión si un jugador de la MLB será elegido para el Salón de la Fama?».

En la primera parte cargaste los datos desde una base de datos SQLite. Esta vez los cargarás desde archivos CSV. Sean Lahman recopiló estos datos en su sitio web.

Importación de datos

Necesitarás leer los siguientes CSV en DataFrames de pandas:

  • Master.csv aporta nombres de jugadores, fecha de nacimiento (DOB) e información biográfica;
  • Fielding.csv contiene estadísticas defensivas;
  • Batting.csv contiene estadísticas de bateo;
  • también leerás AwardsPlayers.csv con los premios ganados por los jugadores;
  • AllstarFull.csv incluye todas las apariciones en el All-Star Game;
  • necesitarás los datos de votación al Salón de la Fama en HallOfFame.csv;
  • y Appearances.csv, con el detalle de posiciones en las que actuó cada jugador.

No te preocupes si algún concepto no está claro aún: lo irás viendo a medida que avances.

Primero, descarga los CSV desde aquí. Idealmente, guarda todos los datos en una carpeta específica para este proyecto. Luego abre tu terminal o arranca Jupyter Notebook y ponte manos a la obra.

Carga pandas como pd y lee cada CSV. Incluirás solo ciertas columnas; puedes revisar las disponibles aquí.

# Import data to DataFramesimport pandas as pd# Read in the CSV filesmaster_df = pd.read_csv('Master.csv',usecols=['playerID','nameFirst','nameLast','bats','throws','debut','finalGame'])fielding_df = pd.read_csv('Fielding.csv',usecols=['playerID','yearID','stint','teamID','lgID','POS','G','GS','InnOuts','PO','A','E','DP'])batting_df = pd.read_csv('Batting.csv')awards_df = pd.read_csv('AwardsPlayers.csv', usecols=['playerID','awardID','yearID'])allstar_df = pd.read_csv('AllstarFull.csv', usecols=['playerID','yearID'])hof_df = pd.read_csv('HallOfFame.csv',usecols=['playerID','yearid','votedBy','needed_note','inducted','category'])appearances_df = pd.read_csv('Appearances.csv')

Nota: en este tutorial, los datos se cargarán por ti.

Limpieza y preprocesamiento

Ahora que has importado los datos, puedes empezar a limpiarlos y preprocesarlos. Como trabajas con 7 CSV, conviene revisarlos uno a uno: en las siguientes secciones, veremos los DataFrames por separado en este orden: batting_df, fielding_df, awards_df, allstar_df, hof_df, appearances_df y master_df.

batting_df

Empezarás con el DataFrame batting_df. Imprime las primeras filas con head():

# Print first few rows of `batting_df`
batting_df.______

Consejo: como has importado todas las columnas de esta tabla, utiliza el atributo columns para verlas y entender con qué trabajas. Usa también cualquier otra función de Pandas para inspeccionar tus datos a fondo.

Al inspeccionar los resultados, notarás tres cosas sobre batting_df y sus columnas:

  1. Hay muchas IDs. Es normal: los datos provienen de una base de datos y todas las tablas se relacionan entre sí, como es habitual en bases de datos relacionales.
  • playerID, identificador del jugador;
  • yearID, el año;
  • teamID, el equipo en el que juega;
  • lgID, la liga.
  1. Como esperarías en batting_df, hay muchas estadísticas de los jugadores; por ejemplo:
  • G número de partidos,
  • AB turnos al bate,
  • R carreras,
  • H hits,
  • HR home runs,
  • RBI carreras impulsadas,
  • SO strikeouts.
  1. Los datos no están en formato ordenado (tidy). Si no te suena el concepto, aquí tienes una explicación detallada de tidy data aquí. Asegurar el formato tidy es crucial: en resumen, cada fila debe ser una observación (en este caso, la carrera de un jugador) y cada columna, una variable. La mayoría de filas actuales son temporadas individuales; debes agregarlas para que cada jugador tenga una sola fila con su carrera.

Para conseguirlo, crearás diccionarios para cada jugador dentro de un diccionario maestro. Agregarás las estadísticas de cada jugador a su diccionario y luego convertirás el diccionario principal en un DataFrame.

Para empezar, crea un diccionario para estadísticas del jugador y otro para los años jugados. Agrega las estadísticas de batting_df al diccionario player_stats y los años jugados a years_played:

# Initialize dictionaries for player stats and years playedplayer_stats = {}years_played = {}# Create dictionaries for player stats and years played from `batting_df`for i, row in batting_df.iterrows():    playerID = row['playerID']    if playerID in player_stats:        player_stats[playerID]['G'] = player_stats[playerID]['G'] + row['G']        player_stats[playerID]['AB'] = player_stats[playerID]['AB'] + row['AB']        player_stats[playerID]['R'] = player_stats[playerID]['R'] + row['R']        player_stats[playerID]['H'] = player_stats[playerID]['H'] + row['H']        player_stats[playerID]['2B'] = player_stats[playerID]['2B'] + row['2B']        player_stats[playerID]['3B'] = player_stats[playerID]['3B'] + row['3B']        player_stats[playerID]['HR'] = player_stats[playerID]['HR'] + row['HR']        player_stats[playerID]['RBI'] = player_stats[playerID]['RBI'] + row['RBI']        player_stats[playerID]['SB'] = player_stats[playerID]['SB'] + row['SB']        player_stats[playerID]['BB'] = player_stats[playerID]['BB'] + row['BB']        player_stats[playerID]['SO'] = player_stats[playerID]['SO'] + row['SO']        player_stats[playerID]['IBB'] = player_stats[playerID]['IBB'] + row['IBB']        player_stats[playerID]['HBP'] = player_stats[playerID]['HBP'] + row['HBP']        player_stats[playerID]['SH'] = player_stats[playerID]['SH'] + row['SH']        player_stats[playerID]['SF'] = player_stats[playerID]['SF'] + row['SF']        years_played[playerID].append(row['yearID'])    else:        player_stats[playerID] = {}        player_stats[playerID]['G'] = row['G']        player_stats[playerID]['AB'] = row['AB']        player_stats[playerID]['R'] = row['R']        player_stats[playerID]['H'] = row['H']        player_stats[playerID]['2B'] = row['2B']        player_stats[playerID]['3B'] = row['3B']        player_stats[playerID]['HR'] = row['HR']        player_stats[playerID]['RBI'] = row['RBI']        player_stats[playerID]['SB'] = row['SB']        player_stats[playerID]['BB'] = row['BB']        player_stats[playerID]['SO'] = row['SO']        player_stats[playerID]['IBB'] = row['IBB']        player_stats[playerID]['HBP'] = row['HBP']        player_stats[playerID]['SH'] = row['SH']        player_stats[playerID]['SF'] = row['SF']        years_played[playerID] = []        years_played[playerID].append(row['yearID'])

Ahora recorre el diccionario years_played y añade el número de años jugados por cada jugador a player_stats:

# Iterate through `years_played` and add the number of years played to `player_stats`for k, v in years_played.items():    player_stats[k]['Years_Played'] = len(list(set(v)))

fielding_df

Seguimos con fielding_df. Esta tabla contiene más estadísticas de jugadores y es muy similar a batting_df. Como ya agregaste estadísticas por jugador en el diccionario player_stats, conviene hacer lo mismo con las de fielding_df.

Concretamente, añadirás nuevas claves a cada diccionario de jugador dentro de player_stats mientras iteras por fielding_df. Primero crea una fielder_list para identificar qué jugadores ya tienen claves de fildeo inicializadas, y sumar correctamente:

# Initialize `fielder_list`fielder_list = []# Add fielding stats to `player_stats` from `fielding_df`for i, row in fielding_df.iterrows():    playerID = row['playerID']    Gf = row['G']    GSf = row['GS']    POf = row['PO']    Af = row['A']    Ef = row['E']    DPf = row['DP']    if playerID in player_stats and playerID in fielder_list:        player_stats[playerID]['Gf'] = player_stats[playerID]['Gf'] + Gf        player_stats[playerID]['GSf'] = player_stats[playerID]['GSf'] + GSf        player_stats[playerID]['POf'] = player_stats[playerID]['POf'] + POf        player_stats[playerID]['Af'] = player_stats[playerID]['Af'] + Af        player_stats[playerID]['Ef'] = player_stats[playerID]['Ef'] + Ef        player_stats[playerID]['DPf'] = player_stats[playerID]['DPf'] + DPf    else:        fielder_list.append(playerID)        player_stats[playerID]['Gf'] = Gf        player_stats[playerID]['GSf'] = GSf        player_stats[playerID]['POf'] = POf        player_stats[playerID]['Af'] = Af        player_stats[playerID]['Ef'] = Ef        player_stats[playerID]['DPf'] = DPf

awards_df

Vamos ahora con awards_df: a lo largo de su carrera, los jugadores pueden recibir distintos premios por su rendimiento. Este DataFrame recoge los premios ganados.

Una buena forma de inspeccionarlo es imprimir los premios únicos con unique(). O prueba con head(), tail(), info(), etc., para conocer mejor los datos.

Consejo: del primer bloque sabes que solo leíste tres columnas: playerID, awardID y yearID.

# Print out unique awards from `awards_df`
print(awards_df['awardID'].________)

Como ves, hay bastantes premios en awards_df… ¿los conoces todos?

Como comentamos en la Parte I, conocer bien los datos es clave. Por ejemplo, los mejores premios a incluir son Most Valuable Player, Rookie of the Year, Gold Glove, Silver Slugger y World Series MVP, algo que concluí basándome en mi experiencia siguiendo la MLB.

Podríamos determinar qué premios se correlacionan más con entrar al Salón de la Fama, pero requeriría bastante más código.

Ahora añade un conteo de cada premio en el diccionario de cada jugador dentro de player_stats: primero filtra awards_df para cada premio, creando cinco series. Luego crea una lista llamada awards_list con las cinco series. Como hiciste con fielding_df, crea listas para identificar qué claves se han añadido en player_stats para cada premio. Crea una lista lists con esas cinco listas. Ya puedes iterar por awards_list y contar cuántos de cada premio tiene cada jugador:

# Create DataFrames for each awardmvp = awards_df[awards_df['awardID'] == 'Most Valuable Player']roy = awards_df[awards_df['awardID'] == 'Rookie of the Year']gg = awards_df[awards_df['awardID'] == 'Gold Glove']ss = awards_df[awards_df['awardID'] == 'Silver Slugger']ws_mvp = awards_df[awards_df['awardID'] == 'World Series MVP']# Include each DataFrame in `awards_list`awards_list = [mvp,roy,gg,ss,ws_mvp]# Initialize lists for each of the above DataFramesmvp_list = []roy_list = []gg_list = []ss_list = []ws_mvp_list = []# Include each of the above lists in `lists`lists = [mvp_list,roy_list,gg_list,ss_list,ws_mvp_list]# Add a count for each award for each player in `player_stats`for index, v in enumerate(awards_list):    for i, row in v.iterrows():        playerID = row['playerID']        award = row['awardID']        if playerID in player_stats and playerID in lists[index]:            player_stats[playerID][award] += 1        else:            lists[index].append(playerID)            player_stats[playerID][award] = 1

allstar_df

El DataFrame allstar_df indica qué jugadores aparecieron en All-Star Games. Es un partido de exhibición a mitad de temporada. La MLB tiene dos ligas: Americana y Nacional. Los 25 mejores de cada liga representan a su liga.

Como antes, itera por allstar_df y añade un conteo de apariciones al diccionario de cada jugador en player_stats.

# Initialize `allstar_list`allstar_list = []# Add a count for each Allstar game appearance for each player in `player_stats`for i, row in allstar_df.iterrows():    playerID = row['playerID']    if playerID in player_stats and playerID in allstar_list:        player_stats[playerID]['AS_games'] += 1    else:        allstar_list.append(playerID)        player_stats[playerID]['AS_games'] = 1

hof_df

Seguimos con hof_df, que recoge qué jugadores, mánagers y directivos fueron nominados o elegidos para el Salón de la Fama y por qué vía.

¡Añadir esta información a player_stats puede ser muy valioso! Para ello, filtra hof_df para incluir solo jugadores elegidos al Salón. Luego recorre hof_df para marcar en player_stats quiénes fueron elegidos y cómo:

# filter `hof_df` to include only instances where a player was inducted into the Hall of Famehof_df = hof_df[(hof_df['inducted'] == 'Y') & (hof_df['category'] == 'Player')]# Indicate which players in `player_stats` were inducted into the Hall of Famefor i, row in hof_df.iterrows():    playerID = row['playerID']    if playerID in player_stats:        player_stats[playerID]['HoF'] = 1        player_stats[playerID]['votedBy'] = row['votedBy']

player_stats

Ya has reunido datos de batting_df, fielding_df, awards_df, allstar_df y hof_df en el diccionario player_stats. Falta trabajar con appearances_df, pero ahora es buen momento para convertir player_stats en DataFrame.

Usa pandas con from_dict() para crear stats_df:

# Convert `player_stats` into a DataFramestats_df = pd.DataFrame.from_dict(player_stats, orient='index')

El DataFrame stats_df usa el playerID como índice. Añade la columna playerID a partir del índice y después haz un inner join con master_df.

Recuerda: un inner join selecciona filas de ambas tablas solo si hay coincidencia entre columnas. Aquí lo usas porque quieres mantener la información común entre stats_df y master_df.

# Add a column for playerID from the `stats_df` index
stats_df['playerID'] = stats_df._____

# Join `stats_df` and `master_df`
master_df = master_df.join(________,on='playerID',how='inner', rsuffix='mstr')

# Inspect first rows of `master_df`
master_df.______

appearances_df

El último DataFrame que debes revisar antes de crear nuevas variables es appearances_df. Contiene el número de apariciones de cada jugador en cada posición por año.

Imprime las primeras filas con head():

print(appearances_df.______)

Consejo: usa la consola de IPython para inspeccionar con info(), tail(), y los atributos columns e index. Verás que appearances_df tiene 21 columnas; algunas son claras (IDs ya vistas) y otras más crípticas como G_3b, G_dh, etc.

Ahora conviene consultar la información del README:

  • G_all Total de partidos jugados
  • GS Partidos como titular
  • G_batting Partidos en los que bateó
  • G_defense Partidos en los que jugó a la defensiva
  • G_p Partidos como pitcher
  • G_c Partidos como catcher
  • G_1b Primera base
  • G_2b Segunda base
  • G_3b Tercera base
  • G_ss Shortstop (parador en corto)
  • G_lf Jardín izquierdo
  • G_cf Jardín central
  • G_rf Jardín derecho
  • G_of Outfielder
  • G_dh Bateador designado
  • G_ph Bateador emergente
  • G_pr Corredor emergente

Como ves, hay mucha información en appearances_df que debes extraer. También hay que agregar apariciones por posición para obtener totales de carrera.

Como recordarás de la primera parte, a lo largo de la historia de la MLB hubo eras con más o menos carreras por partido. Esto afecta mucho a las estadísticas de un jugador. Los votantes del Salón de la Fama lo tienen en cuenta, así que tu modelo también debe hacerlo.

Para recoger esta información, crea un diccionario llamado pos_dict. Recorre appearances_df y añade un diccionario por jugador como hiciste con player_stats. Agrega las apariciones por posición usando la posición como clave y el número de apariciones como valor. Agrega además el número de partidos jugados en cada era usando la era como clave y el conteo de partidos como valor.

# Initialize a dictionarypos_dict = {}# Iterate through `appearances_df`# Add a count for the number of appearances for each player at each position# Also add a count for the number of games played for each player in each era.for i, row in appearances_df.iterrows():    ID = row['playerID']    year = row['yearID']    if ID in pos_dict:        pos_dict[ID]['G_all'] = pos_dict[ID]['G_all'] + row['G_all']        pos_dict[ID]['G_p'] = pos_dict[ID]['G_p'] + row['G_p']        pos_dict[ID]['G_c'] = pos_dict[ID]['G_c'] + row['G_c']        pos_dict[ID]['G_1b'] = pos_dict[ID]['G_1b'] + row['G_1b']        pos_dict[ID]['G_2b'] = pos_dict[ID]['G_2b'] + row['G_2b']        pos_dict[ID]['G_3b'] = pos_dict[ID]['G_3b'] + row['G_3b']        pos_dict[ID]['G_ss'] = pos_dict[ID]['G_ss'] + row['G_ss']        pos_dict[ID]['G_lf'] = pos_dict[ID]['G_lf'] + row['G_lf']        pos_dict[ID]['G_cf'] = pos_dict[ID]['G_cf'] + row['G_cf']        pos_dict[ID]['G_rf'] = pos_dict[ID]['G_rf'] + row['G_rf']        pos_dict[ID]['G_of'] = pos_dict[ID]['G_of'] + row['G_of']        pos_dict[ID]['G_dh'] = pos_dict[ID]['G_dh'] + row['G_dh']        if year < 1920:            pos_dict[ID]['pre1920'] = pos_dict[ID]['pre1920'] + row['G_all']        elif year >= 1920 and year <= 1941:            pos_dict[ID]['1920-41'] = pos_dict[ID]['1920-41'] + row['G_all']        elif year >= 1942 and year <= 1945:            pos_dict[ID]['1942-45'] = pos_dict[ID]['1942-45'] + row['G_all']        elif year >= 1946 and year <= 1962:            pos_dict[ID]['1946-62'] = pos_dict[ID]['1946-62'] + row['G_all']        elif year >= 1963 and year <= 1976:            pos_dict[ID]['1963-76'] = pos_dict[ID]['1963-76'] + row['G_all']        elif year >= 1977 and year <= 1992:            pos_dict[ID]['1977-92'] = pos_dict[ID]['1977-92'] + row['G_all']        elif year >= 1993 and year <= 2009:            pos_dict[ID]['1993-2009'] = pos_dict[ID]['1993-2009'] + row['G_all']        elif year > 2009:            pos_dict[ID]['post2009'] = pos_dict[ID]['post2009'] + row['G_all']    else:        pos_dict[ID] = {}        pos_dict[ID]['G_all'] = row['G_all']        pos_dict[ID]['G_p'] = row['G_p']        pos_dict[ID]['G_c'] = row['G_c']        pos_dict[ID]['G_1b'] = row['G_1b']        pos_dict[ID]['G_2b'] = row['G_2b']        pos_dict[ID]['G_3b'] = row['G_3b']        pos_dict[ID]['G_ss'] = row['G_ss']        pos_dict[ID]['G_lf'] = row['G_lf']        pos_dict[ID]['G_cf'] = row['G_cf']        pos_dict[ID]['G_rf'] = row['G_rf']        pos_dict[ID]['G_of'] = row['G_of']        pos_dict[ID]['G_dh'] = row['G_dh']        pos_dict[ID]['pre1920'] = 0        pos_dict[ID]['1920-41'] = 0        pos_dict[ID]['1942-45'] = 0        pos_dict[ID]['1946-62'] = 0        pos_dict[ID]['1963-76'] = 0        pos_dict[ID]['1977-92'] = 0        pos_dict[ID]['1993-2009'] = 0        pos_dict[ID]['post2009'] = 0        if year < 1920:            pos_dict[ID]['pre1920'] = row['G_all']        elif year >= 1920 and year <= 1941:            pos_dict[ID]['1920-41'] = row['G_all']        elif year >= 1942 and year <= 1945:            pos_dict[ID]['1942-45'] = row['G_all']        elif year >= 1946 and year <= 1962:            pos_dict[ID]['1946-62'] = row['G_all']        elif year >= 1963 and year <= 1976:            pos_dict[ID]['1963-76'] = row['G_all']        elif year >= 1977 and year <= 1992:            pos_dict[ID]['1977-92'] = row['G_all']        elif year >= 1993 and year <= 2009:            pos_dict[ID]['1993-2009'] = row['G_all']        elif year > 2009:            pos_dict[ID]['post2009'] = row['G_all']

Ahora que el diccionario está completo, conviértelo a DataFrame con from_dict():

# Convert the `pos_dict` to a DataFramepos_df = pd.DataFrame.from_dict(pos_dict, orient='index')

Imprime las columnas e inspecciona un poco más:

# Print the columns
print(pos_df._______)

# Print the first rows
print(pos_df.______)

A continuación, calcula el porcentaje de veces que cada jugador actuó en cada posición y en cada era.

Primero, crea una lista pos_col_list a partir de las columnas de pos_df y elimina la cadena ‘G_all’. Luego crea columnas nuevas en pos_df para el porcentaje por posición y por era, iterando por pos_col_list y dividiendo los partidos en cada posición/era entre el total del jugador. Por último, imprime las primeras filas:

# Create a list from the columns of `pos_df`
pos_col_list = pos_df.columns.________

# Remove the string 'G_all'
pos_col_list.remove('G_all')

# Loop through the list and divide each column by the players total games played
for col in pos_col_list:
    column = col + '_percent'
    pos_df[column] = pos_df[col] / pos_df['G_all']

# Print out the first rows of `pos_df`    
print(pos_df._______)

Como mencionamos, este proyecto se centra en infielders y outfielders, ya que cada posición se juzga de forma distinta para el Salón de la Fama. Infielders y outfielders se evalúan de forma similar; pitchers y catchers se juzgan más por defensa, lo cual queda fuera del alcance de este proyecto.

Como en los primeros años muchos jugadores actuaban en varias posiciones, elimina pitchers y catchers filtrando a quienes jugaron más del 10% de sus partidos en alguna de esas posiciones.

# Filter `pos_df` to eliminate players who played 10% or more of their games as Pitchers or Catchers
pos_df = pos_df[(pos_df['G_p_percent'] < 0.1) & (pos_df['G_c_percent'] < 0.1)]

# Get info on `pos_df`
pos_df.______

master_df

Recuerda que hiciste un inner join entre master_df y stats_df (compuesto con datos de batting_df, fielding_df, awards_df, allstar_df y hof_df) para lograr un dataset más uniforme con estadísticas y datos maestros (nombres, fecha de debut y biografía).

Ahora quieres unir el master_df uniforme con pos_df; ¡ojo! Aquí debes usar un right join porque solo quieres conservar a los jugadores que estén en pos_df:

# Join `pos_df` and `master_df`
master_df = master_df.join(pos_df,on='playerID',how='right')

# Print out the first rows of `master_df`
print(master_df.______)

Ahora filtra master_df para dejar solo jugadores que fueron elegidos por votación o que no entraron en absoluto.

Algunos entran por el Veterans Committee o por comités especiales. Suelen ser seleccionados por motivos más allá de las estadísticas —difíciles de cuantificar para tu modelo—, así que es mejor excluirlos.

# Replace NA values with `None`
master_df['votedBy'] = master_df['votedBy'].fillna('NoNe')

# Filter `master_df` to include only players who were voted into the Hall of Fame or Players who did not make it at all
master_df = master_df[(master_df['_______'] == 'None') | (master_df['votedBy'] == 'BBWAA') | (master_df['votedBy'] == 'Run Off')]

# Inspect `master_df`
master_df.info()

Las columnas bats y throws deben convertirse a numérico: crea una función que convierta R en 1 y L en 0. Usa apply() para crear bats_R y throws_R:

Las columnas debut y finalGame son cadenas. Debes extraer el año. Importa datetime, convierte ambas a objeto datetime con pd.to_datetime(), luego extrae el año con dt.strftime('%Y') y conviértelo a numérico con pd.to_numeric().

# Import datetime 
from datetime import datetime

# Convert the `debut` column to datetime
master_df['debut'] =  pd.___________(master_df['debut'])
# Convert the `finalGame` column to datetime
master_df['finalGame'] = pd.to_datetime(master_df['finalGame'])

# Create new columns for debutYear and finalYear
master_df['debutYear'] = pd.to_numeric(master_df['debut'].dt.strftime('%Y'), errors='coerce')
master_df['finalYear'] = _____________(master_df['finalGame'].dt.strftime('%Y'), errors='coerce')

# Return the first rows of `master_df`
print(master_df.head())

Actualmente, master_df tiene algunas columnas innecesarias. Usa drop() sobre master_df y crea un nuevo DataFrame llamado df. Imprime las columnas de df y muestra el recuento de nulos por columna.

En la primera parte, trabajabas con estadísticas de equipo y los nulos podían deberse a datos faltantes (p. ej., es raro que un equipo pase una temporada sin un double play).

Con jugadores, es más probable que los nulos sean ceros.

Por eso, crea una lista de columnas numéricas y recórrela para rellenar nulos con 0 usando fillna(). Luego usa dropna() para eliminar nulos restantes.

Temas

Relacionado

Tutorial

Clasificación de bosques aleatorios con Scikit-Learn