back to index

MLB Hall of Fame Predictor

A scikit-learn decision tree that guesses whether a current MLB player would get voted into Cooperstown, built for the Google Cloud x MLB hackathon.

repo: https://github.com/EricSpencer00/MLB-Hackathon


An entry for the Google Cloud x MLB hackathon in December. The prompt was open-ended around baseball data. The Hall of Fame angle works because the question is fun to argue about and the dataset is clean enough to model without spending the whole weekend on data prep.

The data is the Lahman Baseball Database (1871-2023): every player, every season, batting, pitching and fielding splits, awards, and the Hall of Fame ballot history. The pitch was to take a current player's career-to-date stats, feed them into a model trained on the historical record of who got inducted and who did not, and get a probability back.

Most of the actual hackathon time went into merging.py. Lahman ships as a dozen separate CSVs that have to be aggregated per player and then joined: sum every season of batting into a career line, do the same for pitching and fielding, count awards per player, then left-join the Hall of Fame ballot table to get the inducted label. There is a lot of room to be clever here (era adjustments, advanced metrics, position weighting) and none of it is done.

The features are mostly the obvious counting stats: games, at-bats, runs, hits, doubles, triples, home runs, RBIs, walks, strikeouts on the batting side; wins, losses, ERA, saves, strikeouts on the pitching side; putouts, assists, errors on the fielding side; plus an AwardsCount column aggregated from AwardsPlayers.csv. Twenty-three features in total, all numeric.


feature_columns = [
    'BattingGames', 'AtBats', 'Runs', 'Hits', 'Doubles', 'Triples', 'HomeRuns', 'RBIs', 'Walks', 'Strikeouts',
    'Wins', 'Losses', 'PitchingGames', 'GamesStarted', 'CompleteGames', 'Saves', 'PitchingStrikeouts', 'EarnedRunAverage',
    'FieldingGames', 'Putouts', 'Assists', 'Errors', 'AwardsCount'
]

features = data[feature_columns]
labels = data['inducted']

train_features, test_features, train_labels, test_labels = train_test_split(
    features, labels, test_size=0.2, random_state=42
)

skmodel = DecisionTreeClassifier()
skmodel.fit(train_features, train_labels)
print('accuracy is:', skmodel.score(test_features, test_labels))

The model is a vanilla scikit-learn DecisionTreeClassifier: no tuning, no ensemble, no cross-validation beyond a single 80/20 split. The class imbalance is real. A tiny fraction of MLB players ever make the Hall, so raw accuracy is a misleading number to chase, and a "predict not inducted for everyone" baseline already gets into the high 90s. Precision and recall on the inducted class are the numbers to check before claiming the model does anything. A fair-weekend version would swap in a random forest or gradient boosting with proper stratified cross-validation.

The cloud half of the project is in interact.py and a Vertex AI endpoint. train.py has the commented-out GCS upload: the idea was to push model.joblib to a bucket, deploy it to Vertex, and hit it from anywhere with a prediction call. The wiring is in place, but the endpoint never got deployed before the deadline, so that half is a sketch.

Things another weekend would buy: real per-season time series instead of career totals (the dataset is called _Timeseries for a reason, but it gets collapsed here), position-aware features, era adjustments so a 1920s slugger is not penalized for not having modern counting stats, and a frontend where a current player is picked and a probability comes back. Evaluating against players who recently became eligible but are not inducted yet would also be a more honest test than a random split.

GitHub Repo