The NFL Combine and Pro Bowl Cornerbacks: A Data Exploration

github repository for this project

Introduction

I’ve been interested for a while in exploring the NFL combine dataset and understanding how useful it is in predicting success in the NFL. What follows is a brief exploration of the data, specifically for prospective NFL cornerbacks.

Combine and Pro Bowl Basics

The Pro Bowl is an all-star game between the NFL’s top players. The 88 players who make the Pro Bowl are voted on by coaches, players, and fans and chosen from the pool of ~1700 NFL players. We assume here that a player making the Pro Bowl equates to individual success in the NFL.

Why Cornerbacks?

Core Question

Data

Pro Bowl Data: The list of NFL Pro Bowl players was pulled from Pro Football Reference. The data is also in .csv format in the github repository for this project.

Data Exploration and Visualization

~5% of cornerbacks in NFL Combine history make the Pro Bowl

non-Pro Bowler v. Pro Bowler visualizations

import seaborn as sns
import matplotlib.pyplot as plt
#data to plot
plots = ['college', 'height in', 'weight lbs', 'hand_size in',
'arm_length in', '40 yard', 'bench_press','vert_leap in',
'broad_jump in', 'shuttle', '3cone', '60yd_shuttle']
#plot all the columns
for plot in plots:
sns.catplot(x="is_pro_bowl", y = plot, kind="violin", data=cornerbacks, height=8.5, aspect=.9)
plt.savefig(plot + '.png')

Pro Bowl Cornerbacks seem to be taller and heavier

Height (in)

Weight (lbs)

Pro Bowl Cornerbacks seems to be faster (lower 40yd dash time), but not necessarily stronger (similar bench press reps)

40 Yard Dash (s)

Bench Press (reps)

Pro Bowl Cornerbacks seem to be able to jump higher (vertical) and farther (broad jump)

Vertical Leap (in)

Broad Jump (in)

Pro Bowl Cornerbacks seem to be quicker with better acceleration when changing direction.*

*Shuttle run tests require an athlete to run while changing directions

Shuttle (s)

60yd Shuttle

Summary of Visual Observations

#lets see the means across columns for the two groups
cornerbacks.groupby('is_pro_bowl').mean()

Hypothesis Testing

#============Hypothesis Testing======================
from scipy.stats import ttest_ind
for plot in plots:
#sns.catplot(x="is_pro_bowl", y = plot, kind="violin", data=cornerbacks, height=8.5, aspect=.9)
t1 = cornerbacks[cornerbacks['is_pro_bowl'] == 1][plot]
t2 = cornerbacks[cornerbacks['is_pro_bowl'] == 0][plot]
t1_mean = np.mean(t1)
t2_mean = np.mean(t2)
t1_std = np.std(t1)
t2_std = np.std(t2)
ttest,pval = ttest_ind(t1, t2)


if pval <0.05:
print('====== ', plot, " ======")
print("p-value", pval)
print("we reject null hypothesis [" + plot + "] IS sig different!")
print("Pro Bowl mean value:",t1_mean," stdev: ",t1_std)
print("Non- PB mean value:", t2_mean," stdev: ",t2_std)
print("==== END ", plot, " ====")
print(" ")

The results show statistically significant differences between non-Pro Bowl and Pro Bowl cornerbacks for the NFL Combine tests which include: 40 yard dash, broad jump, shuttle, and 60 yd shuttle.

======  40 yard  ======
p-value 1.3708582248077843e-05
we reject null hypothesis [40 yard] IS sig different!
Pro Bowl mean value: 4.490860324171952 stdev: 0.09324748995664504
Non- PB mean value: 4.546474208961045 stdev: 0.10696579488451659
==== END 40 yard ====

====== broad_jump in ======
p-value 0.008383869956918569
we reject null hypothesis [broad_jump in] IS sig different!
Pro Bowl mean value: 121.37864864864866 stdev: 6.273889060387517
Non- PB mean value: 119.6110296617372 stdev: 5.516273484274304
==== END broad_jump in ====

====== shuttle ======
p-value 0.018817831255597058
we reject null hypothesis [shuttle] IS sig different!
Pro Bowl mean value: 4.148978537012703 stdev: 0.16950481445681398
Non- PB mean value: 4.187172800326174 stdev: 0.1321607800289746
==== END shuttle ====

====== 60yd_shuttle ======
p-value 0.005133910470464446
we reject null hypothesis [60yd_shuttle] IS sig different!
Pro Bowl mean value: 11.382047488584476 stdev: 0.23683360234089437
Non- PB mean value: 11.46302547313403 stdev: 0.24082481449027213
==== END 60yd_shuttle ====

Machine Learning Model / Logistic Regression

I’m not a machine learning expert (yet), but I know enough to hack a model together for my own entertainment. My process to make a model to predict NFL Pro Bowl cornerbacks follows.

Training/Test Split and Oversampling

Our data are also highly imbalanced with only around 5% of the data representing Pro Bowl cornerbacks and 95% representing non-Pro Bowlers. There may be too few instances of Pro Bowlers for our model to train on. Therefore, we use the synthetic minority oversampling technique (SMOTE) to create synthetic data points for the model training process.

#==========OVER SAMPLING USING SMOTE=================
X = cornerbacks.loc[:, cornerbacks.columns != 'is_pro_bowl']
y = cornerbacks.loc[:, cornerbacks.columns == 'is_pro_bowl']

from imblearn.over_sampling import SMOTE

os = SMOTE(random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
columns = X_train.columns

os_data_X, os_data_y = os.fit_sample(X_train, y_train) #oversample training data
os_data_X = pd.DataFrame(data=os_data_X,columns=columns)
os_data_y= pd.DataFrame(data=os_data_y,columns=['is_pro_bowl'])

# we can Check the numbers of our data
print("length of oversampled data is ",len(os_data_X))
print("Number of non-Pro Bowlers in oversampled data",len(os_data_y[os_data_y['is_pro_bowl']==0]))
print("Number of Pro Bowlers",len(os_data_y[os_data_y['is_pro_bowl']==1]))
print("Proportion of non-Pro Bowler data in oversampled data is ",len(os_data_y[os_data_y['is_pro_bowl']==0])/len(os_data_X))
print("Proportion of Pro Bowler data in oversampled data is ",len(os_data_y[os_data_y['is_pro_bowl']==1])/len(os_data_X))

Modeling

#==========LOGISTIC REGRESSION MODEL FIT=============
from sklearn.linear_model import LogisticRegression
X=os_data_X[cols]
y=os_data_y['is_pro_bowl']
logreg = LogisticRegression(C=200, class_weight=None, dual=False,
fit_intercept=True, intercept_scaling=1,
max_iter=1000, multi_class='ovr', n_jobs=1, penalty='l2', random_state=None, solver='liblinear', tol=0.0001, verbose=0, warm_start=False)

logreg.fit(X, y)
#predicting test set results and calculating accuracy
y_pred = logreg.predict(X_test)
print('Accuracy of logistic regression classifier on test set: {:.2f}'
.format(logreg.score(X_test, y_test)))

Is the Model Any Good?

(0: non-Pro Bowl, 1: Pro Bowl)

Precision, Recall, and F-Measure

                precision    recall  f1-score   support
0 0.96 0.58 0.72 285
1 0.12 0.71 0.21 24
accuracy 0.59 309
macro avg 0.54 0.64 0.47 309

Confusion Matrix

While we correctly identified 71% (17 out of 24) of the Pro Bowl cornerbacks in the test set, we ended up with a high false positive rate (120 false positives out of 137 predicted Pro Bowlers).

Conclusions

However, the model to predict which Combine cornerbacks will actually make the Pro Bowl in the future produces too many false positives to be useful in a real world scenario. Future prediction models might benefit from exploring other non-binary success metrics such as career pass breakups, interceptions, or salary.

References: github repository for this project, NFL combine data, Pro Bowl Data, logistic regression process

Curious human, data-driven, founder

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store