NFL Draft Projection Models
Beginner
10 min read
1 views
Nov 27, 2025
# NFL Draft Projection Models
## Overview
Projecting college players to the NFL is one of the most challenging analytics problems in football. Success requires combining college performance metrics, physical measurements, game film analysis, and historical translation patterns.
## Key Evaluation Components
### College Production Metrics
- **Yards per attempt/carry**: Efficiency measures
- **Touchdown rate**: Red zone effectiveness
- **Turnover rate**: Ball security for QBs/RBs
- **Pressure rate**: QB performance under duress
- **Target share**: WR usage and opportunity
### Physical Measurements
- **Height/Weight/Speed**: Position-specific thresholds
- **Arm length**: Critical for OL, DL, CB
- **Explosion metrics**: Vertical jump, broad jump
- **Agility scores**: 3-cone drill, shuttle
- **RAS (Relative Athletic Score)**: Composite athletic rating
### Context Factors
- **Competition level**: P5 vs G5 vs FCS
- **Conference strength**: SEC vs MAC adjustment
- **Age**: Younger players have higher upside
- **Experience**: Years as starter
- **Team quality**: Supporting cast considerations
## R Analysis with cfbfastR
```r
library(cfbfastR)
library(dplyr)
library(ggplot2)
library(randomForest)
library(caret)
# Load college player statistics
player_stats_2023 <- cfbd_stats_season_player(
year = 2023,
season_type = "regular"
)
# Focus on skill positions for draft projection
qb_stats <- player_stats_2023 %>%
filter(category == "passing") %>%
group_by(player, team, conference) %>%
summarise(
completions = sum(stat[statType == "completions"]),
attempts = sum(stat[statType == "attempts"]),
yards = sum(stat[statType == "passingYards"]),
tds = sum(stat[statType == "passingTDs"]),
ints = sum(stat[statType == "interceptions"]),
.groups = "drop"
) %>%
mutate(
comp_pct = completions / attempts * 100,
yards_per_att = yards / attempts,
td_rate = tds / attempts * 100,
int_rate = ints / attempts * 100,
td_int_ratio = tds / pmax(ints, 1)
)
# Calculate QB draft grade (simplified)
qb_draft_grade <- qb_stats %>%
filter(attempts >= 200) %>% # Minimum attempts threshold
mutate(
# Normalize metrics to 0-100 scale
yards_score = scale(yards_per_att) * 10 + 50,
td_score = scale(td_rate) * 10 + 50,
int_score = scale(-int_rate) * 10 + 50, # Negative because lower is better
comp_score = scale(comp_pct) * 10 + 50,
# Composite draft grade
draft_grade = (
yards_score * 0.3 +
td_score * 0.3 +
int_score * 0.2 +
comp_score * 0.2
),
# Draft round projection (simplified)
projected_round = case_when(
draft_grade >= 70 ~ "Round 1-2",
draft_grade >= 60 ~ "Round 3-4",
draft_grade >= 50 ~ "Round 5-7",
TRUE ~ "Undrafted"
)
) %>%
arrange(desc(draft_grade))
print("Top 25 QB Draft Prospects by Statistical Grade:")
print(qb_draft_grade %>%
select(player, team, yards_per_att, td_rate, int_rate,
draft_grade, projected_round) %>%
head(25))
# Visualize QB draft prospects
top_qbs <- qb_draft_grade %>% head(30)
ggplot(top_qbs, aes(x = yards_per_att, y = td_rate)) +
geom_point(aes(size = draft_grade, color = projected_round), alpha = 0.7) +
scale_color_manual(values = c("Round 1-2" = "darkgreen",
"Round 3-4" = "blue",
"Round 5-7" = "orange",
"Undrafted" = "red")) +
geom_text(aes(label = player), size = 2.5, vjust = -1, hjust = 0.5) +
labs(
title = "QB Draft Prospects - Statistical Profile",
x = "Yards per Attempt",
y = "TD Rate (%)",
size = "Draft Grade",
color = "Projected Round"
) +
theme_minimal()
# Historical draft analysis: college production vs NFL success
# This would require NFL performance data - using synthetic example
historical_qbs <- data.frame(
player = c("QB1", "QB2", "QB3", "QB4", "QB5"),
college_ypa = c(9.2, 8.5, 8.8, 7.9, 9.0),
college_td_rate = c(6.5, 5.8, 6.2, 5.0, 6.8),
college_comp_pct = c(68, 64, 66, 61, 70),
draft_position = c(1, 15, 8, 42, 3),
nfl_success = c(85, 60, 70, 45, 80) # Composite NFL rating
)
# Correlation analysis
cor_ypa <- cor(historical_qbs$college_ypa, historical_qbs$nfl_success)
cor_td <- cor(historical_qbs$college_td_rate, historical_qbs$nfl_success)
cor_comp <- cor(historical_qbs$college_comp_pct, historical_qbs$nfl_success)
print("\nCorrelation with NFL Success:")
print(paste("Yards per Attempt:", round(cor_ypa, 3)))
print(paste("TD Rate:", round(cor_td, 3)))
print(paste("Completion %:", round(cor_comp, 3)))
# Running backs: yards after contact, elusiveness
rb_stats <- player_stats_2023 %>%
filter(category == "rushing") %>%
group_by(player, team) %>%
summarise(
carries = sum(stat[statType == "rushingAttempts"]),
yards = sum(stat[statType == "rushingYards"]),
tds = sum(stat[statType == "rushingTDs"]),
.groups = "drop"
) %>%
mutate(
yards_per_carry = yards / carries,
td_rate = tds / carries * 100
) %>%
filter(carries >= 100)
# Draft projection for RBs
rb_draft_grade <- rb_stats %>%
mutate(
ypc_score = scale(yards_per_carry) * 10 + 50,
volume_score = scale(carries) * 10 + 50,
td_score = scale(td_rate) * 10 + 50,
draft_grade = (
ypc_score * 0.4 +
volume_score * 0.3 +
td_score * 0.3
),
projected_round = case_when(
draft_grade >= 65 ~ "Round 1-3",
draft_grade >= 55 ~ "Round 4-5",
draft_grade >= 45 ~ "Round 6-7",
TRUE ~ "Priority FA"
)
) %>%
arrange(desc(draft_grade))
print("\nTop 20 RB Draft Prospects:")
print(rb_draft_grade %>%
select(player, team, yards_per_carry, carries, tds,
draft_grade, projected_round) %>%
head(20))
# Visualize RB prospects
top_rbs <- rb_draft_grade %>% head(25)
ggplot(top_rbs, aes(x = yards_per_carry, y = carries)) +
geom_point(aes(size = draft_grade, color = projected_round), alpha = 0.7) +
scale_color_manual(values = c("Round 1-3" = "darkgreen",
"Round 4-5" = "blue",
"Round 6-7" = "orange",
"Priority FA" = "red")) +
labs(
title = "RB Draft Prospects - Production Profile",
x = "Yards per Carry",
y = "Total Carries",
size = "Draft Grade",
color = "Projected Round"
) +
theme_minimal()
```
## Python Implementation
```python
import pandas as pd
import numpy as np
import requests
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler
def get_player_stats(year, category='passing'):
"""Fetch player statistics from CFB Data API"""
url = "https://api.collegefootballdata.com/stats/player/season"
params = {
'year': year,
'seasonType': 'regular',
'category': category
}
response = requests.get(url, params=params)
return pd.DataFrame(response.json())
# Load QB statistics
qb_stats = get_player_stats(2023, 'passing')
# Clean and prepare QB data
qb_df = qb_stats[qb_stats['stat'] >= 200].copy() # Min 200 attempts
# Calculate key metrics per QB
qb_metrics = qb_df.groupby(['player', 'team', 'conference']).first()
# Add calculated metrics (simplified - real data would have completions, TDs, etc.)
# For demonstration, creating synthetic derived metrics
np.random.seed(42)
qb_metrics['comp_pct'] = np.random.uniform(55, 72, len(qb_metrics))
qb_metrics['yards_per_att'] = np.random.uniform(6.5, 10.5, len(qb_metrics))
qb_metrics['td_rate'] = np.random.uniform(3.5, 8.0, len(qb_metrics))
qb_metrics['int_rate'] = np.random.uniform(1.0, 4.5, len(qb_metrics))
# Calculate composite draft grade
def calculate_qb_draft_grade(row):
"""Calculate QB draft grade based on statistics"""
# Normalize each metric
scaler = StandardScaler()
# Weights for each metric
grade = (
row['comp_pct'] * 0.20 +
row['yards_per_att'] * 10 * 0.35 +
row['td_rate'] * 10 * 0.30 +
(10 - row['int_rate']) * 3 * 0.15
)
return grade
qb_metrics['draft_grade'] = qb_metrics.apply(calculate_qb_draft_grade, axis=1)
# Project draft round
def project_draft_round(grade):
if grade >= 70:
return "Round 1"
elif grade >= 60:
return "Round 2-3"
elif grade >= 50:
return "Round 4-5"
elif grade >= 40:
return "Round 6-7"
else:
return "Undrafted"
qb_metrics['projected_round'] = qb_metrics['draft_grade'].apply(project_draft_round)
# Sort by draft grade
qb_prospects = qb_metrics.sort_values('draft_grade', ascending=False).reset_index()
print("Top 25 QB Draft Prospects:")
print(qb_prospects[['player', 'team', 'comp_pct', 'yards_per_att',
'td_rate', 'int_rate', 'draft_grade',
'projected_round']].head(25))
# Load RB statistics
rb_stats = get_player_stats(2023, 'rushing')
rb_df = rb_stats[rb_stats['stat'] >= 100].copy() # Min 100 carries
# Calculate RB metrics
rb_df['yards_per_carry'] = np.random.uniform(4.0, 7.5, len(rb_df))
rb_df['receptions'] = np.random.randint(10, 60, len(rb_df))
rb_df['receiving_yards'] = rb_df['receptions'] * np.random.uniform(7, 12, len(rb_df))
rb_df['total_tds'] = np.random.randint(3, 18, len(rb_df))
# RB draft grade
def calculate_rb_draft_grade(row):
"""Calculate RB draft grade"""
grade = (
row['yards_per_carry'] * 10 * 0.40 +
(row['stat'] / 250) * 50 * 0.25 + # Volume adjusted
row['total_tds'] * 3 * 0.25 +
row['receptions'] * 0.5 * 0.10
)
return grade
rb_df['draft_grade'] = rb_df.apply(calculate_rb_draft_grade, axis=1)
rb_df['projected_round'] = rb_df['draft_grade'].apply(
lambda x: "Round 1-2" if x >= 75 else
"Round 3-4" if x >= 60 else
"Round 5-7" if x >= 45 else "Priority FA"
)
rb_prospects = rb_df.sort_values('draft_grade', ascending=False)
print("\nTop 20 RB Draft Prospects:")
print(rb_prospects[['player', 'team', 'yards_per_carry', 'stat',
'total_tds', 'receptions', 'draft_grade',
'projected_round']].head(20))
# Visualizations
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
# 1. QB Draft Board
top_qbs = qb_prospects.head(20)
colors = top_qbs['projected_round'].map({
'Round 1': 'darkgreen',
'Round 2-3': 'green',
'Round 4-5': 'orange',
'Round 6-7': 'red',
'Undrafted': 'gray'
})
axes[0, 0].barh(range(len(top_qbs)), top_qbs['draft_grade'], color=colors)
axes[0, 0].set_yticks(range(len(top_qbs)))
axes[0, 0].set_yticklabels(top_qbs['player'], fontsize=8)
axes[0, 0].set_xlabel('Draft Grade')
axes[0, 0].set_title('Top 20 QB Draft Prospects')
axes[0, 0].invert_yaxis()
# 2. QB Statistical Profile
axes[0, 1].scatter(top_qbs['yards_per_att'], top_qbs['td_rate'],
s=top_qbs['draft_grade']*5, c=top_qbs['int_rate'],
cmap='RdYlGn_r', alpha=0.6)
axes[0, 1].set_xlabel('Yards per Attempt')
axes[0, 1].set_ylabel('TD Rate (%)')
axes[0, 1].set_title('QB Efficiency Profile (size=grade, color=INT rate)')
axes[0, 1].grid(alpha=0.3)
# 3. RB Draft Board
top_rbs = rb_prospects.head(20)
rb_colors = top_rbs['projected_round'].map({
'Round 1-2': 'darkgreen',
'Round 3-4': 'green',
'Round 5-7': 'orange',
'Priority FA': 'red'
})
axes[1, 0].barh(range(len(top_rbs)), top_rbs['draft_grade'], color=rb_colors)
axes[1, 0].set_yticks(range(len(top_rbs)))
axes[1, 0].set_yticklabels(top_rbs['player'], fontsize=8)
axes[1, 0].set_xlabel('Draft Grade')
axes[1, 0].set_title('Top 20 RB Draft Prospects')
axes[1, 0].invert_yaxis()
# 4. RB Production vs Efficiency
axes[1, 1].scatter(top_rbs['stat'], top_rbs['yards_per_carry'],
s=top_rbs['total_tds']*10, c=top_rbs['draft_grade'],
cmap='viridis', alpha=0.6)
axes[1, 1].set_xlabel('Total Carries')
axes[1, 1].set_ylabel('Yards per Carry')
axes[1, 1].set_title('RB Volume vs Efficiency (size=TDs, color=grade)')
axes[1, 1].grid(alpha=0.3)
plt.tight_layout()
plt.show()
```
## Position-Specific Translation Factors
### Quarterback
- **Completion %**: 68%+ translates better
- **Yards per Attempt**: 8.0+ indicates NFL arm talent
- **Under Pressure**: Performance vs blitz critical
- **Downfield Passing**: 20+ yard attempts success rate
### Running Back
- **Yards After Contact**: NFL durability predictor
- **Receiving Ability**: Pass-catching RBs more valuable
- **Pass Protection**: Often overlooked, highly important
- **Size/Speed Combo**: 215+ lbs with 4.5- 40 time ideal
### Wide Receiver
- **Target Share**: 25%+ shows alpha WR traits
- **Yards per Route Run**: Better than yards per catch
- **Contested Catch Rate**: 1-on-1 winning ability
- **Route Running**: Separation vs press/zone coverage
### Offensive Line
- **Pass Block Win Rate**: Advanced metric via PFF
- **Run Block Win Rate**: Creating movement
- **Length**: Arm length > 33" preferred for tackles
- **Anchor Strength**: Bench press, play strength
### Defensive Line
- **Pressure Rate**: QB disruption frequency
- **Run Stop %**: TFL + stops / total snaps
- **Explosion**: 10-yard split, broad jump
- **Length**: Arm length for edge rushers
### Linebacker
- **Tackle Rate**: Solo tackles per snap
- **Coverage Grade**: Slot/TE coverage ability
- **Blitz Success**: Pressure rate on blitzes
- **Athleticism**: 4.6- 40 time, 7.0- 3-cone
### Defensive Back
- **Yards per Cover Snap**: Yards allowed/snap in coverage
- **Ball Production**: INTs + PBUs per target
- **Speed**: 4.4- 40 time for CBs
- **Length**: Arm length + hand size for CBs
## Machine Learning Approach
### Feature Engineering
1. **Production Metrics**: Stats adjusted for competition level
2. **Physical Traits**: Combine measurables (RAS scores)
3. **Context Variables**: Team quality, conference, age
4. **Advanced Metrics**: PFF grades, EPA contributions
### Model Types
- **Regression**: Predict draft position (1-260)
- **Classification**: Draft round or NFL success tier
- **Survival Analysis**: Career length prediction
- **Ensemble**: Combine multiple models for robustness
### Validation
- Historical accuracy: Past draft projections vs actuals
- Cross-validation: Different years/conferences
- Positional differences: Separate models per position
## Key Insights
### High-Value College Traits
- **Efficiency over volume**: Yards per attempt > total yards
- **Age-adjusted production**: Younger players with higher upside
- **Competition level**: P5 production more predictive
- **Versatility**: Multi-position ability increases value
### Common Projection Errors
- Over-valuing measurables vs production
- Ignoring competition level adjustments
- Recency bias (final season weighed too heavily)
- Position scarcity leading to reaches
### Draft Value Analysis
- Rounds 1-2: Hit rate ~60%, expected value high
- Round 3: Sweet spot for value vs risk
- Rounds 4-5: Volume approach, developmental prospects
- Rounds 6-7: Dart throws, special teams contributors
## Resources
- [MockDraftable](https://www.mockdraftable.com/) - Combine data visualization
- [Pro Football Focus Draft Guide](https://www.pff.com/draft)
- [NFL Draft Scout](https://www.nfldraftscout.com/)
- [The Athletic NFL Draft Coverage](https://theathletic.com/nfl/draft/)
Discussion
Have questions or feedback? Join our community discussion on
Discord or
GitHub Discussions.
Table of Contents
Related Topics
Quick Actions