<jupyter_start><jupyter_text>Here is my analysis of the NFL Draft over the past decade. My goal here is to see how accurately I can predict the results of the 1st round of the NFL Draft based solely on a player's college stats in their last college season and their combine results. Some factors not considered in this analysis are a player's college film and their individualized team scouting reports. In some ways, this analysis will be a test to see how much one's draft position is based on stats and physique versus film and personality. Or the two may not be entirely independent. Does a player's stats sometimes affect how a scout will review their film? I will be using the 2010-2019 drafts as my training set, and the 2020 draft as my test set. The most obvious measure of success would be comparing my results to the actual 2020 draft and seeing how many picks I got exactly correct. However, correctly predicting exact draft positions beyond the first couple is nearly impossible for a few reasons. Firstly, a team may draft a 'worse' player that fits a team need over a better player, and it is quite hard to implement team needs, which change each season, into my model. Secondly, there are always multiple draft day trades that are virtually impossible to predict and account for without insider sources around the league. And thirdly, sometimes news stories break right before the draft that can greatly affect a player's draft stock, particularly when these stories pertain to off field behavioral issues. Even experts with better access to this information than I do would be thrilled to get even 10 picks exactly correct in the first round.Therefore, I will instead be treating my model like a board of the best players available, regardless of draft order, and I will measure my success in two ways. First, I will look at the percent of players I correctly predict to go in the first round, and compare that to the percent that an expert's ranking got correct. Second, I will use my model as a predictor of NFL success by looking at how my predicted first round players performed in the NFL, and I will compare that to both the expert's rankings and the actual draft order.<jupyter_code>import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import re
%matplotlib inline

pd.set_option('display.max_columns', None)

df = pd.read_csv('cleaner_draft_data_10_20.csv')

df = df.sort_values('Unnamed: 0')

df.drop(df.loc[:,df.columns.str.contains('NFL')], axis=1, inplace=True)
df.drop(df.loc[:,df.columns[47:56]], axis=1, inplace=True)
df.drop('Pts', axis=1, inplace=True)
d = {'College/Univ':'College'}
di = {'Unnamed: 0':'index'}
df.rename(d, axis=1, inplace=True)
df.rename(di, axis=1, inplace=True)

df.set_index('index', inplace=True)
df.drop([2070, 2330, 102, 1812, 320, 2741, 971, 972, 973, 1583, 1584, 1586, 316, 2361, 2406, 2517, 582, 1494, 1044, 2051, 1695, 2417, 1981, 2175, 471, 1233, 864, 2096], inplace=True)
df.reset_index(inplace=True)
df.drop('index', axis=1, inplace=True)
df[['Ht', 'Wt', '40yd', 'Vertical', 'Bench', 'Broad Jump', '3Cone', 'Shuttle']] = df.groupby('Pos')['Ht', 'Wt', '40yd', 'Vertical', 'Bench', 'Broad Jump', '3Cone', 'Shuttle'].transform(lambda x: x.fillna(x.mean()))

for i in df.columns[14:]:
    df[i].fillna(0, inplace=True)
    
df['Draft.Yr'] = df['Drafted (tm/rnd/yr)'].str.split('/')
years = []

for index, item in df.iterrows():
    try:
        year = item['Draft.Yr'][-1]
        
    except TypeError:
        year = np.nan
    years.append(year)
    
df['Year'] = years

df['Year'].fillna(method='ffill', inplace=True)
df['Year'] = df['Year'].astype('int64')
df.drop(['Draft.Yr', 'Drafted (tm/rnd/yr)'], axis=1, inplace=True)

df['AV/Yr'] = df['CarAV']/(df['To']-df['Year'] + 1)

sb09 = {'Cmp':39, 'Pass.Att':69, 'Pct':56.5, 'Pass.Yds':562, 'Pass.TD':2, 'Pass.Int':0, 'Rate':134.5, 'Rush.Att':4, 'Rush.Yds':-18, 'Rush.Avg':-4.5, 'Rush.TD':0}
sb08 = {'Cmp':328, 'Pass.Att':483, 'Pct':67.9, 'Pass.Yds':4720, 'Pass.TD':50, 'Pass.Int':8, 'Rate':180.8, 'Rush.Att':42, 'Rush.Yds':47, 'Rush.Avg':1.1, 'Rush.TD':5}
cw00 = {'Cmp':0, 'Pass.Att':0, 'Pct':0, 'Pass.Yds':0, 'Pass.TD':0, 'Pass.Int':0, 'Rate':0, 'Rush.Att':0, 'Rush.Yds':0, 'Rush.Avg':0, 'Rush.TD':0}
cw14 = {'Cmp':228, 'Pass.Att':358, 'Pct':63.7, 'Pass.Yds':3111, 'Pass.TD':25, 'Pass.Int':10, 'Rate':154.1, 'Rush.Att':138, 'Rush.Yds':642, 'Rush.Avg':4.7, 'Rush.TD':6}

df.iloc[0:1] = df.iloc[0:1].replace(to_replace=sb09, value=sb08)
df.iloc[1509:1510] = df.iloc[1509:1510].replace(to_replace=cw00, value=cw14)<jupyter_output><ipython-input-2-7102b48dfb51>:1: FutureWarning: Indexing with multiple keys (implicitly converted to a tuple of keys) will be deprecated, use a list instead.
  df[['Ht', 'Wt', '40yd', 'Vertical', 'Bench', 'Broad Jump', '3Cone', 'Shuttle']] = df.groupby('Pos')['Ht', 'Wt', '40yd', 'Vertical', 'Bench', 'Broad Jump', '3Cone', 'Shuttle'].transform(lambda x: x.fillna(x.mean()))<jupyter_text>First I import and clean the data. The notebook I used to import and do most of the cleaning will be uploaded separately. All the data is collected from Pro-Football-Reference.com. Since I'm only looking at college stats I dropped the NFL stats. I do however want to keep each player's Approximate Value per Year, as it will be useful for looking backwards to see if more value comes from players drafted in the first round based on my model, the experts' models, or the actual draft.For missing height, weight, and combine data, I filled it with the mean value based on each position. I also scraped just the year out of the column 'Drafted (tm/rnd/yr)'.Finally, there are two known irregularites in my data that may cause some errors in my results. Those are players with duplicate names, and injury shortened final college seasons. The only injury seasons I changed (replaced with previous season's stats) were <NAME>'s  and <NAME>'s since they missed over half their games and were both picked in the top 2, so that seems like it would have a significant impact on the results. Beyond that, there's just too many players to sift through and determine if injury significantly hampered their seasons. For players with identical names, I tried to sift through as many as I could and deleted the extra copies that were created based on how I created this dataframe. I know I got all the first rounders cleaned correctly. There may be some deeper round picks that I missed.<jupyter_code>qbs = df[df['Pos']=='QB']
qbs.drop('Bench', axis=1, inplace=True)
qb_cols = qbs.columns[:7].append(qbs.columns[13:25].append(qbs.columns[46:53].append(qbs.columns[57:])))
qbs = qbs[qb_cols]

a, b = np.polyfit(qbs['Rate'], qbs['Pick'], 1)
x = np.arange(10, 230)
plt.figure()
sns.relplot(x='Rate', y='Pick', data=qbs)
plt.plot(x, (a*x+b))
plt.title('Rating Correlation')

r = np.corrcoef(qbs['Rate'], qbs['Pick'])

print('Rating Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Pass.Yds', y='Pick', data=qbs)
a, b = np.polyfit(qbs['Pass.Yds'], qbs['Pick'], 1)
x = np.arange(700, 5700)
plt.plot(x, a*x+b)
plt.title('Yards Correlation')

r = np.corrcoef(qbs['Pass.Yds'], qbs['Pick'])

print('Yards Correlation:', r[0,1])


sns.relplot(x='Pass.TD', y='Pick', data=qbs)
a, b = np.polyfit(qbs['Pass.TD'], qbs['Pick'], 1)
x = np.arange(0, 60)
plt.plot(x, a*x+b)
plt.title('TDs Correlation')

r = np.corrcoef(qbs['Pass.TD'], qbs['Pick'])

print('TDs Correlation:', r[0,1])<jupyter_output>C:\Python\Anaconda\lib\site-packages\pandas\core\frame.py:3990: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  return super().drop(<jupyter_text>Originally, I thought running a regression model on each position separately would yield more accurate results since different stats are of different relevance for each position. After some epxerimentation, that turned out to be wrong, and I got more accurate results when I ran the regression on the draft data was a whole. However, I still kept this EDA broken down by position. For each position I chose the few stats I though would make the biggest difference, and plotted them against pick position to find their correlation. Note: my results with each model broken down by position will also be uploaded in a separate notebook.<jupyter_code>wrs = df[df['Pos']=='WR']
wr_cols = wrs.columns[:6].append(wrs.columns[13:14].append(wrs.columns[21:29].append(wrs.columns[42:])))

wrs = wrs[wr_cols]

plt.figure()
sns.relplot(x='Rec', y='Pick', data=wrs, kind='scatter')
a, b = np.polyfit(wrs['Rec'], wrs['Pick'], 1)
x = np.arange(0, 140)
plt.plot(x, a*x+b)
plt.title('Receptions Correlations')

r = np.corrcoef(wrs['Rec'], wrs['Pick'])
print('Rec. Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Rec.Yds', y='Pick', data=wrs, kind='scatter')
a, b = np.polyfit(wrs['Rec.Yds'], wrs['Pick'], 1)
x = np.arange(0, 2000)
plt.plot(x, a*x+b)
plt.title('Yards Correlation')

r = np.corrcoef(wrs['Rec.Yds'], wrs['Pick'])
print('Yards Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Rec.TD', y='Pick', data=wrs, kind='scatter')
a, b = np.polyfit(wrs['Rec.TD'], wrs['Pick'], 1)
x = np.arange(0, 25)
plt.plot(x, a*x+b)
plt.title('TDs Correlation')

r = np.corrcoef(wrs['Rec.TD'], wrs['Pick'])
print('TDs Correlation:', r[0,1])

plt.figure()
sns.relplot(x='40yd', y='Pick', data=wrs, kind='scatter')
a, b = np.polyfit(wrs['40yd'], wrs['Pick'], 1)
x = np.linspace(4, 5, 50)
plt.plot(x, a*x+b)
plt.title('40yd Correlation')

r = np.corrcoef(wrs['40yd'], wrs['Pick'])
print('40yd Correlation:', r[0,1])
rbs = df[df['Pos']=='RB']
rb_cols = rbs.columns[:6].append(rbs.columns[13:14].append(rbs.columns[21:29].append(rbs.columns[42:])))

rbs = rbs[rb_cols]

plt.figure()
sns.relplot(x='Rush.Yds', y='Pick', data=rbs, kind='scatter')
a, b = np.polyfit(rbs['Rush.Yds'], rbs['Pick'], 1)
x = np.arange(0, 2500)
plt.plot(x, a*x+b)
plt.title('Yards Correlations')

r = np.corrcoef(rbs['Rush.Yds'], rbs['Pick'])
print('Yards Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Rush.Avg', y='Pick', data=rbs, kind='scatter')
a, b = np.polyfit(rbs['Rush.Avg'], rbs['Pick'], 1)
x = np.arange(0, 20)
plt.plot(x, a*x+b)
plt.title('Y/A Correlation')

r = np.corrcoef(rbs['Rush.Avg'], rbs['Pick'])
print('Y/A Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Rush.TD', y='Pick', data=rbs, kind='scatter')
a, b = np.polyfit(rbs['Rush.TD'], rbs['Pick'], 1)
x = np.arange(0, 30)
plt.plot(x, a*x+b)
plt.title('TDs Correlation')

r = np.corrcoef(rbs['Rush.TD'], rbs['Pick'])
print('TDs Correlation:', r[0,1])
tes = df[df['Pos']=='TE']
te_cols = tes.columns[:6].append(tes.columns[13:14].append(tes.columns[21:29].append(tes.columns[46:54].append(tes.columns[58:]))))
tes = tes[te_cols]

plt.figure()
sns.relplot(x='Rec', y='Pick', data=tes, kind='scatter')
a, b = np.polyfit(tes['Rec'], tes['Pick'], 1)
x = np.arange(0, 120)
plt.plot(x, a*x+b)
plt.title('Receptions Correlations')

r = np.corrcoef(tes['Rec'], tes['Pick'])
print('Rec. Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Rec.Yds', y='Pick', data=tes, kind='scatter')
a, b = np.polyfit(tes['Rec.Yds'], tes['Pick'], 1)
x = np.arange(0, 1500)
plt.plot(x, a*x+b)
plt.title('Yards Correlation')

r = np.corrcoef(tes['Rec.Yds'], tes['Pick'])
print('Yards Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Rec.TD', y='Pick', data=tes, kind='scatter')
a, b = np.polyfit(tes['Rec.TD'], tes['Pick'], 1)
x = np.arange(0, 20)
plt.plot(x, a*x+b)
plt.title('TDs Correlation')

r = np.corrcoef(tes['Rec.TD'], tes['Pick'])
print('TDs Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Wt', y='Pick', data=tes, kind='scatter')
a, b = np.polyfit(tes['Wt'], tes['Pick'], 1)
x = np.arange(200, 275)
plt.plot(x, a*x+b)
plt.title('Wt Correlation')

r = np.corrcoef(tes['Wt'], tes['Pick'])
print('Wt Correlation:', r[0,1])
db_pos = ['DB', 'CB', 'S']
dbs = df[df.Pos.str.startswith(tuple(db_pos))]

dbs_col = dbs.columns[:6].append(dbs.columns[13:14].append(dbs.columns[29:]))

dbs = dbs[dbs_col]

plt.figure()
sns.relplot(x='Def.Int', y='Pick', data=dbs, kind='scatter')
a, b = np.polyfit(dbs['Def.Int'], dbs['Pick'], 1)
x = np.arange(0, 10)
plt.plot(x, a*x+b)
plt.title('Int Correlation')

r = np.corrcoef(dbs['Def.Int'], dbs['Pick'])
print('Int Correlation:', r[0,1])

plt.figure()
sns.relplot(x='PD', y='Pick', data=dbs, kind='scatter')
a, b = np.polyfit(dbs['PD'], dbs['Pick'], 1)
x = np.arange(0, 25)
plt.plot(x, a*x+b)
plt.title('PDs Correlation')

r = np.corrcoef(dbs['PD'], dbs['Pick'])
print('PDs Correlation:', r[0,1])<jupyter_output>Int Correlation: -0.06559403061425353
PDs Correlation: -0.17792223434513826<jupyter_text>Defensive backs present an interesting case here. While they do have cumulative stats like tackles, passes defensed, and interceptions, being a very good DB usually means that you won't accumulate as many of these stats as other DBs because the ball won't be thrown your way as often. This can be seen in the graphs above where there aren't strong correlations between PDs or INTs and draft position.<jupyter_code>lb_pos = ['LB', 'OLB', 'ILB']
lbs = df[df.Pos.str.startswith(tuple(lb_pos))]

lbs_col = lbs.columns[:6].append(lbs.columns[13:14].append(lbs.columns[29:42].append(lbs.columns[46:54].append(lbs.columns[58:]))))

lbs = lbs[lbs_col]

plt.figure()
sns.relplot(x='Tot', y='Pick', data=lbs, kind='scatter')
a, b = np.polyfit(lbs['Tot'], lbs['Pick'], 1)
x = np.arange(0, 200)
plt.plot(x, a*x+b)
plt.title('Tackles Correlations')

r = np.corrcoef(lbs['Tot'], lbs['Pick'])
print('Tackles Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Loss', y='Pick', data=lbs, kind='scatter')
a, b = np.polyfit(lbs['Loss'], lbs['Pick'], 1)
x = np.arange(0, 30)
plt.plot(x, a*x+b)
plt.title('TFL Correlation')

r = np.corrcoef(lbs['Loss'], lbs['Pick'])
print('TFL Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Sk', y='Pick', data=lbs, kind='scatter')
a, b = np.polyfit(lbs['Sk'], lbs['Pick'], 1)
x = np.arange(0, 20)
plt.plot(x, a*x+b)
plt.title('Sacks Correlation')

r = np.corrcoef(lbs['Sk'], lbs['Pick'])
print('Sacks Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Wt', y='Pick', data=lbs, kind='scatter')
a, b = np.polyfit(lbs['Wt'], lbs['Pick'], 1)
x = np.arange(200, 300)
plt.plot(x, a*x+b)
plt.title('Wt Correlation')

r = np.corrcoef(lbs['Wt'], lbs['Pick'])
print('Wt Correlation:', r[0,1])<jupyter_output>Tackles Correlation: -0.14164474290851123
TFL Correlation: -0.2787275317204962
Sacks Correlation: -0.32878346435054545
Wt Correlation: -0.14875432983968395<jupyter_text>Unlike DBs, linebackers want to be near the ball as often as possible, so the correlation of defensive stats like sacks and tackles for loss seems accurate here.<jupyter_code>dl_pos = ['DE', 'DT', 'DL', 'NT']
dls = df[df.Pos.str.startswith(tuple(dl_pos))]

dls_col = dls.columns[:6].append(dls.columns[13:14].append(dls.columns[29:42].append(dls.columns[46:54].append(dls.columns[58:]))))

dls = dls[dls_col]

lb_pos = ['LB', 'OLB', 'ILB']
dls = df[df.Pos.str.startswith(tuple(lb_pos))]

dls_col = dls.columns[:6].append(dls.columns[13:14].append(dls.columns[29:42].append(dls.columns[46:54].append(dls.columns[58:]))))

dls = dls[dls_col]

plt.figure()
sns.relplot(x='Tot', y='Pick', data=dls, kind='scatter')
a, b = np.polyfit(dls['Tot'], dls['Pick'], 1)
x = np.arange(0, 200)
plt.plot(x, a*x+b)
plt.title('Tackles Correlations')

r = np.corrcoef(dls['Tot'], dls['Pick'])
print('Tackles Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Loss', y='Pick', data=dls, kind='scatter')
a, b = np.polyfit(dls['Loss'], dls['Pick'], 1)
x = np.arange(0, 30)
plt.plot(x, a*x+b)
plt.title('TFL Correlation')

r = np.corrcoef(dls['Loss'], dls['Pick'])
print('TFL Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Sk', y='Pick', data=dls, kind='scatter')
a, b = np.polyfit(dls['Sk'], dls['Pick'], 1)
x = np.arange(0, 20)
plt.plot(x, a*x+b)
plt.title('Sacks Correlation')

r = np.corrcoef(dls['Sk'], dls['Pick'])
print('Sacks Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Wt', y='Pick', data=dls, kind='scatter')
a, b = np.polyfit(dls['Wt'], dls['Pick'], 1)
x = np.arange(200, 300)
plt.plot(x, a*x+b)
plt.title('Wt Correlation')

r = np.corrcoef(dls['Wt'], dls['Pick'])
print('Wt Correlation:', r[0,1])


oline =  df[(df['Pos']=='T') | (df['Pos']=='G') | (df['Pos']=='C') | (df['Pos']=='OL')]

plt.figure()
sns.relplot(x='Broad Jump', y='Pick', data=oline, kind='scatter')
a, b = np.polyfit(oline['Broad Jump'], oline['Pick'], 1)
x = np.arange(80, 130)
plt.plot(x, a*x+b)
plt.title('Broad Jump Correlations')

r = np.corrcoef(oline['Broad Jump'], oline['Pick'])
print('Broad Jump Correlation:', r[0,1])

plt.figure()
sns.relplot(x='3Cone', y='Pick', data=oline, kind='scatter')
a, b = np.polyfit(oline['3Cone'], oline['Pick'], 1)
x = np.arange(7, 10)
plt.plot(x, a*x+b)
plt.title('3 Cone Correlation')

r = np.corrcoef(oline['3Cone'], oline['Pick'])
print('3 Cone Correlation:', r[0,1])

plt.figure()
sns.relplot(x='Bench', y='Pick', data=oline, kind='scatter')
a, b = np.polyfit(oline['Bench'], oline['Pick'], 1)
x = np.arange(5, 45)
plt.plot(x, a*x+b)
plt.title('Bench Correlation')

r = np.corrcoef(oline['Bench'], oline['Pick'])
print('Bench:', r[0,1])

plt.figure()
sns.relplot(x='Wt', y='Pick', data=oline, kind='scatter')
a, b = np.polyfit(oline['Wt'], oline['Pick'], 1)
x = np.arange(200, 360)
plt.plot(x, a*x+b)
plt.title('Wt Correlation')

r = np.corrcoef(oline['Wt'], oline['Pick'])
print('Wt Correlation:', r[0,1])<jupyter_output>Broad Jump Correlation: -0.21900778824403735
3 Cone Correlation: 0.11812357291718972
Bench: -0.047799368030032326
Wt Correlation: -0.0493868618029704<jupyter_text>Offensive linemen also present an interesting case here as they essentially have no easily formatable stats on Pro-Football-Reference. Therefore, all I have is their combine data and college conference.<jupyter_code>from sklearn.linear_model import ElasticNet, LinearRegression, Ridge
from sklearn.ensemble import VotingRegressor, RandomForestRegressor
from sklearn.model_selection import cross_val_score, RandomizedSearchCV
from sklearn.metrics import mean_squared_error


rfr = RandomForestRegressor(random_state=0)
en = ElasticNet(random_state=0)
rid = Ridge(random_state=0)
linreg = LinearRegression()
vote = VotingRegressor(estimators = [('rf', rfr), ('en', en), ('rid', rid), ('linreg', linreg)], weights=[1,1,1,1]) 

rel_cols = df.columns[4:5].append(df.columns[14:-2])
player_info = df[['Player', 'Pos', 'Year', 'AV/Yr']]

pre_2020 = df[df['Year']<2020]
yr_2020 = df[df['Year']==2020]

X_train_h = pre_2020[rel_cols]
X_train_h = pd.get_dummies(X_train_h)
y_train = pre_2020['Pick']
X_test_h = yr_2020[rel_cols]
X_test_h = pd.get_dummies(X_test_h)
y_test = yr_2020['Pick']

train_cols = X_train_h.columns.intersection(X_test_h.columns)
test_cols = X_test_h.columns.intersection(X_train_h.columns)

X_train = X_train_h[train_cols]
X_test = X_test_h[test_cols]

#X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=10)

model = vote.fit(X_train, y_train)

y_predict = model.predict(X_test)

mse = mean_squared_error(y_test, y_predict)

print('Mean Squared Error:', mse)

output = pd.DataFrame({'Id':X_test.index, 'Pick':y_predict})
output_2020 = output.merge(player_info, how='left', left_on='Id', right_on=player_info.index)


pff = pd.read_html('https://www.drafttek.com/2020-NFL-Draft-Big-Board/Top-NFL-Draft-Prospects-2020-Page-1.asp', skiprows=1)
pff1 = pd.read_html('https://www.drafttek.com/2020-NFL-Draft-Big-Board/Top-NFL-Draft-Prospects-2020-Page-2.asp', skiprows=1)
pff2 = pd.read_html('https://www.drafttek.com/2020-NFL-Draft-Big-Board/Top-NFL-Draft-Prospects-2020-Page-3.asp', skiprows=1)
bb1 = pff[0]
bb2 = pff1[0]
bb3 = pff2[0]

bb = pd.concat([bb1, bb2, bb3])

big_board = bb[[0, 2]]

d = {0:'Expert_Rank', 2:'Player'}
big_board = big_board.rename(mapper=d, axis=1)

test_df = df[['Pick', 'Player']]
test_df['Actual_Pick'] = test_df['Pick']
test_df = test_df.reset_index()
test_df.drop(['Pick', 'index'] , axis=1, inplace=True)

output_2020 = output_2020.merge(test_df, how='left', left_on='Player', right_on=test_df['Player'])
output_2020.drop(['Id', 'Player_x', 'Player_y'], axis=1, inplace=True)
output_2020['Pick_Ordinal'] = output_2020['Pick'].rank(method='first')
output_2020['Pick_Distance'] = output_2020['Actual_Pick'] - output_2020['Pick_Ordinal']
output_2020.fillna(0, inplace=True)

output_2020 = output_2020.merge(big_board, how='left', on='Player')
output_2020['AV_Rank'] = output_2020['AV/Yr'].rank(method='min', ascending=False)

output_2020.iloc[9] = output_2020.iloc[9].fillna(16)
output_2020.iloc[2] = output_2020.iloc[2].fillna(3)
output_2020.iloc[54] = output_2020.iloc[54].fillna(54)
output_2020.iloc[44] = output_2020.iloc[44].fillna(32)
output_2020.iloc[34] = output_2020.iloc[34].fillna(29)
output_2020.iloc[81] = output_2020.iloc[81].fillna(45)
output_2020.iloc[15] = output_2020.iloc[15].fillna(38)
display(output_2020.sort_values('Pick').head(32))

MSEoP = np.square(np.subtract(output_2020['Actual_Pick'], output_2020['Pick_Ordinal'])).mean()
MSEeR = np.square(np.subtract(output_2020['Actual_Pick'], output_2020['Expert_Rank'])).mean()
print('MSE Ordinal Pick:', MSEoP)
print('MSE Expert Rank:', MSEeR)<jupyter_output><ipython-input-12-42e4a99c56a0>:16: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  test_df['Actual_Pick'] = test_df['Pick']<jupyter_text>Here are my final results. When I don't separate by position and use a voting regressor with random forest, elastic net, ridge and linear regression, I get my most accurate results. When trying any of these regressors on the individual positions, I could get a lower MSE for individual positions, but once I concatenated them the MSE was usually over 5000. I thought splitting by position would give more accurate results, but apparently treating position as its own feature is more advantageous. For some players, it predicted their draft position very well. Particularly for <NAME>, <NAME>, <NAME>, <NAME>, <NAME>, <NAME> and <NAME>, who were all predicted within 5 picks of their actual draft position. Even for players who were slightly more off like <NAME>, <NAME>, <NAME>, <NAME>, and <NAME>, I would still consider these all successes as they all were correctly predicted to go in the first round. There were also some big misses, like <NAME>, <NAME>, and <NAME>, who were over 170 picks away from where they were predicted. Though this is much better than earlier iterations of this model where the two latter players were predicted within the top 10. I think earlier versions weighted height and weight too much, giving a large edge to offensive and defensive linemen, but that seems to be mostly worked out now. Of course, <NAME> looks like a pretty big miss as well, but I'm not surprised there either considering he was essentially the entire offense for Kentucky, a Power 5 team, and the model doesn't take strength of college schedule into account.<jupyter_code>av_ranks = output_2020.sort_values('AV_Rank')

pred_count = av_ranks[(av_ranks['Pick_Ordinal']<= 32) & (av_ranks['AV_Rank']<=32)]
exp_count = av_ranks[(av_ranks['Expert_Rank']<= 32) & (av_ranks['AV_Rank']<=32)]
real_count = av_ranks[(av_ranks['Actual_Pick']<= 32) & (av_ranks['AV_Rank']<=32)]

print('Good Value Model Picks:', pred_count['Player'].count())
print('Good Value Expert Picks:', exp_count['Player'].count())
print('Good Value Real Picks:', real_count['Player'].count())


model_btr_ex = av_ranks[(av_ranks['Pick_Ordinal'] <= 32) & (av_ranks['Expert_Rank'] > 32) & (av_ranks['AV_Rank']<=32)]
exp_better = av_ranks[(av_ranks['Pick_Ordinal'] > 32) & (av_ranks['Expert_Rank'] <= 32) & (av_ranks['AV_Rank']<=32)]
model_btr_nfl = av_ranks[(av_ranks['Pick_Ordinal'] <= 32) & (av_ranks['Actual_Pick'] > 32) & (av_ranks['AV_Rank']<=32)]
nfl_better = av_ranks[(av_ranks['Pick_Ordinal'] > 32) & (av_ranks['Actual_Pick'] <= 32) & (av_ranks['AV_Rank']<=32)]

display(model_btr_ex)
display(exp_better)
display(model_btr_nfl)
display(nfl_better)<jupyter_output>Good Value Model Picks: 15
Good Value Expert Picks: 19
Good Value Real Picks: 22<jupyter_text>First, let's compare my model with the expert's and the actual NFL based on our ability to predict NFL success. I sorted the players in the 2020 NFL draft by Approximate Value and ranked the top 32. These are the players that theoretically should've gone in the first round (with ties it's more than 32, but the point still stands). The top dataframe shows the players worth a first round pick (AV Rank <= 32) that I correctly predicted, that the experts did not. In this case I'm treating my model as a board of best available players rather than an actual draft prediction. The second dataframe shows the opposite. These are the players worth a first a first rounder that the experts predicted that I did not. The next two show the same thing except I'm comparing my model to the NFL itself, with my better predictions on top, and theirs below.I had some mild success here, particularly with predicting the success of RBs and WRs (<NAME>, <NAME>, <NAME>, <NAME>, <NAME>). I think this makes sense because RBs and WRs have some of the largest volumes of both stats and players, meaning they are more normalized and easier to predict. Though QBs and TEs also have large volumes of stats, they have a much smaller volume of players. The few misses here seem to be <NAME>, <NAME>, and <NAME>. <NAME> is technically a miss here, but I predicted him going early 2nd round, so I wouldn't really call that a miss. The only underlying factor I can find is that the correctly predicted players are all a bit taller and heavier than the misses, so the model may still be relying a little too much on height and weight.My biggest misses are defensive players, particularly LBs and DTs. I'm not surprised because these positions don't have as many stats and they don't accumulate as high as, say, receiving yards do for example. This means there's less separation between a good and mediocre, or mediocre and bad college season. Defensive players also seemed highly sensitive to changes in the model during this whole project, while offensive skill players seems less so. There were many models where <NAME>, <NAME>, and <NAME> went in the first round, with Brown going as high as the top 5 in some of them, but they were on the whole less accurate (larger MSE). I think this fits my theory as small changes in weights would have bigger changes if the gap between good and mediocre stats is smaller.My offensive line predictions seem a little all over the place, which again I'm not surprised about. I got some hits (<NAME>, <NAME>) and some misses (<NAME>, <NAME>). The lack fo stats for these positions made it very difficult to accurately predict them, but this model does it the best by far compared to any of the previous I tried. Previous models (particularly Random Forests) almost always had multiple Tackles and Guards with AVs of 2 or less going in the first round, and even the top 5 sometimes. This model accounted for offensive linemen the best, and that's probably the biggest reason why it had the lowest MSE of any I tried.<jupyter_code>output_2020.loc[output_2020['Pick_Ordinal']<=32.0, 'Round1_Pred'] = 1
output_2020.loc[output_2020['Pick_Ordinal'] > 32.0, 'Round1_Pred'] = 0
output_2020.loc[output_2020['Actual_Pick']<=32.0, 'Round1_True'] = 1
output_2020.loc[output_2020['Actual_Pick'] > 32.0, 'Round1_True'] = 0
output_2020.loc[output_2020['Expert_Rank']<=32.0, 'Round1_Ex'] = 1
output_2020.loc[output_2020['Expert_Rank'] > 32.0, 'Round1_Ex'] = 0

display(output_2020.sort_values('Pick_Distance').head(10))

round1_pred = output_2020[output_2020['Round1_Pred']==1]
round1_true = output_2020[output_2020['Round1_True']==1]
round1_ex = output_2020[output_2020['Round1_Ex']==1]

sns.countplot(x='Pos', data=round1_pred)
plt.title('Predicted 1st Round Positions')

plt.figure()
sns.countplot(x='Pos', data=round1_true)
plt.title('Actual 1st Round Positions')

print('Correct 1st Round Predictions:', output_2020['Player'].loc[(output_2020['Round1_Pred']==1) & (output_2020['Round1_True']==1)].count())
print("Expert's Correct 1st Round Predictions:", output_2020['Player'].loc[(output_2020['Round1_Ex']==1) & (output_2020['Round1_True']==1)].count())

print('1st Rnd Approx. Value Sum, Vote Regressor:', round1_pred['AV/Yr'].sum())
print('1st Rnd Approx. Value Sum, Expert Model:', round1_ex['AV/Yr'].sum())
print('1st Rnd Approx. Value Sum, Actual Draft:', round1_true['AV/Yr'].sum())<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.16347120618520958, 'fraction_numerical': 0.04534895815275804, 'mean_word_length': 5.9453407175535755, 'pattern_counts': {'":': 0, '<': 81, '<?xml version=': 0, '>': 69, 'https://': 5, 'lorem ipsum': 0, 'www.': 3, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '33093', 'license': 'MIT', 'n_tokens_mistral': 11679, 'n_tokens_neox': 10413, 'n_words': 3023, 'path': 'NFL_Draft_Analysis_2010-2020_Final.ipynb', 'repo_name': 'jedbell7050/NFL_data_analysis', 'size': '29078'}