Case Study: The $5 Million Kicker Decision
Scenario
The Cleveland Browns have a decision to make. Their veteran kicker, who was 23/27 (85.2%) last season, is a free agent demanding $5 million per year. Meanwhile, an undrafted rookie who excelled in college (91.5% career) is available for the league minimum (~$750K).
The GM asks the analytics department: Is the veteran kicker worth $4.25 million more than the rookie? What does the data tell us about kicker performance and value?
Veteran Kicker Profile: - 6 seasons, 156/186 career (83.9%) - Last season: 23/27 (85.2%) - Made 4/6 from 50+ yards - 0 missed XPs (42/42)
Rookie Kicker Profile: - 4-year college career: 108/118 (91.5%) - Strong leg (5/7 from 50+ in college) - No NFL experience - "Looked great in tryout"
Data Gathering
import nfl_data_py as nfl
import pandas as pd
import numpy as np
# Load historical kicker data
pbp = nfl.import_pbp_data([2018, 2019, 2020, 2021, 2022, 2023])
# Filter to field goals
fgs = pbp[pbp['field_goal_attempt'] == 1].copy()
# Veteran's actual kicks (approximate)
veteran_kicks = fgs[fgs['kicker_player_name'] == 'VETERAN_NAME']
print(f"Veteran career FGs: {len(veteran_kicks)}")
print(f"Veteran career FG%: {(veteran_kicks['field_goal_result'] == 'made').mean():.1%}")
Analysis 1: Expected FG% Adjustment
First, let's see if the veteran actually outperformed expectations:
def expected_fg_pct(distance):
"""League expected FG% by distance."""
if distance <= 30:
return 0.95
elif distance <= 40:
return 0.88
elif distance <= 50:
return 0.75
else:
return 0.55
# Apply to veteran's kicks
veteran_kicks['expected'] = veteran_kicks['kick_distance'].apply(expected_fg_pct)
veteran_kicks['made'] = (veteran_kicks['field_goal_result'] == 'made').astype(int)
# Calculate FG over expected
actual_makes = veteran_kicks['made'].sum()
expected_makes = veteran_kicks['expected'].sum()
fg_over_expected = actual_makes - expected_makes
avg_distance = veteran_kicks['kick_distance'].mean()
print("Veteran Performance Analysis:")
print(f" Actual Makes: {actual_makes}")
print(f" Expected Makes: {expected_makes:.1f}")
print(f" FG Over Expected: {fg_over_expected:+.1f}")
print(f" Average Distance: {avg_distance:.1f} yards")
Finding: Over his career, the veteran has made 2.3 more field goals than expected - approximately 0.4 FGs per year over expected. This is a small positive, but within normal variance.
Analysis 2: Year-to-Year Stability
How predictive is past kicker performance?
# Calculate year-to-year correlations
kicker_yearly = (fgs
.groupby(['kicker_player_name', 'season'])
.agg(
attempts=('field_goal_attempt', 'count'),
fg_pct=('field_goal_result', lambda x: (x == 'made').mean())
)
.query('attempts >= 15')
.reset_index()
)
# Calculate correlation between consecutive seasons
def year_to_year_correlation(df, metric):
correlations = []
for kicker in df['kicker_player_name'].unique():
kicker_data = df[df['kicker_player_name'] == kicker].sort_values('season')
if len(kicker_data) >= 2:
for i in range(len(kicker_data) - 1):
y1 = kicker_data.iloc[i][metric]
y2 = kicker_data.iloc[i+1][metric]
correlations.append((y1, y2))
if correlations:
y1s, y2s = zip(*correlations)
return np.corrcoef(y1s, y2s)[0, 1]
return None
fg_corr = year_to_year_correlation(kicker_yearly, 'fg_pct')
print(f"Year-to-Year FG% Correlation: r = {fg_corr:.3f}")
Finding: Year-to-year FG% correlation is approximately 0.35. This means only about 12% of variance (r²) is explained by previous performance. Past performance is only weakly predictive.
Analysis 3: Rookie Transition Analysis
How do rookies typically perform vs college?
# Find kickers in first NFL season
first_year_kickers = (fgs
.groupby('kicker_player_name')
.agg(
first_season=('season', 'min'),
first_year_attempts=('field_goal_attempt', lambda x: x.iloc[:30].count()),
first_year_pct=('field_goal_result', lambda x: (x.iloc[:30] == 'made').mean())
)
)
# Compare to later years
career_kickers = (fgs
.groupby('kicker_player_name')
.agg(
career_attempts=('field_goal_attempt', 'count'),
career_pct=('field_goal_result', lambda x: (x == 'made').mean())
)
.query('career_attempts >= 50')
)
# Merge for comparison
comparison = first_year_kickers.join(career_kickers)
comparison['improvement'] = comparison['career_pct'] - comparison['first_year_pct']
print("Rookie to Career Comparison:")
print(f" Avg First Year FG%: {comparison['first_year_pct'].mean():.1%}")
print(f" Avg Career FG%: {comparison['career_pct'].mean():.1%}")
print(f" Avg Improvement: {comparison['improvement'].mean():.1%}")
Finding: Rookie kickers average 80-82% in their first year vs career averages around 83-84%. There's typically modest improvement, but the distribution is wide.
Analysis 4: Value of FG Accuracy
What's the actual point value difference between kickers?
# Calculate expected points from FG outcomes
def fg_point_value(made, distance):
"""Calculate point value of FG outcome."""
if made:
return 3.0 # Made FG
else:
# Opponent gets ball at spot of kick (approximately)
opp_start = 100 - distance + 7 # Approximate
opp_ep = -0.03 * opp_start + 2.5 # Simplified EP curve
return -opp_ep
# Calculate difference in expected points
# Veteran: 85% accurate on 30 attempts
# Replacement: 80% accurate on 30 attempts
veteran_expected_points = 0.85 * 30 * 3.0 + 0.15 * 30 * (-0.5)
rookie_expected_points = 0.80 * 30 * 3.0 + 0.20 * 30 * (-0.5)
point_difference = veteran_expected_points - rookie_expected_points
print(f"Season Point Difference (85% vs 80%):")
print(f" Veteran expected: {veteran_expected_points:.1f} points")
print(f" Rookie expected: {rookie_expected_points:.1f} points")
print(f" Difference: {point_difference:.1f} points")
Finding: The difference between an 85% kicker and an 80% kicker on 30 attempts is approximately 5.25 expected points per season. That's less than one touchdown.
Analysis 5: Confidence Intervals
How confident can we be in the veteran's "true" ability?
def fg_confidence_interval(makes, attempts, confidence=0.95):
"""Calculate confidence interval for FG%."""
from scipy import stats
p_hat = makes / attempts
z = stats.norm.ppf((1 + confidence) / 2)
se = np.sqrt(p_hat * (1 - p_hat) / attempts)
lower = p_hat - z * se
upper = p_hat + z * se
return lower, p_hat, upper
# Veteran: 23/27 last season
lower, point, upper = fg_confidence_interval(23, 27)
print(f"Veteran Last Season 95% CI:")
print(f" Point estimate: {point:.1%}")
print(f" 95% CI: [{lower:.1%}, {upper:.1%}]")
# Career: 156/186
lower_c, point_c, upper_c = fg_confidence_interval(156, 186)
print(f"\nVeteran Career 95% CI:")
print(f" Point estimate: {point_c:.1%}")
print(f" 95% CI: [{lower_c:.1%}, {upper_c:.1%}]")
Finding: The veteran's "true" ability could be anywhere from 70% to 95% based on last season (sample size of 27). Even his career numbers only narrow it to approximately 78-89%.
Analysis 6: Cost-Benefit Calculation
What would the veteran need to be worth $4.25M more?
# Value per point in the NFL
# Teams typically value 1 win at ~$3-4M in FA
# 1 win ≈ 3-4 expected points margin per game * 17 games
# Rough estimate: 1 point ≈ $80K
dollars_per_point = 80000
# How many points better must veteran be?
salary_difference = 4250000
required_point_advantage = salary_difference / dollars_per_point
print(f"Cost-Benefit Analysis:")
print(f" Salary Difference: ${salary_difference:,}")
print(f" Required Point Advantage: {required_point_advantage:.1f} points/year")
# What FG% advantage does that require?
# On 30 attempts, each 1% = 0.3 FGs = ~1 point
required_fg_advantage = required_point_advantage / 30 * 0.01
print(f" Required FG% Advantage: {required_fg_advantage:.1%}")
print(f" (Veteran would need to be ~18 percentage points better)")
Finding: To justify $4.25M more in salary, the veteran would need to be worth approximately 53 more points per year - an impossible margin for a kicker. Even if he's 10% better (which is unlikely), that's only worth about $240K.
Visualization
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
fig.suptitle('Kicker Value Analysis', fontsize=14, fontweight='bold')
# Plot 1: FG% distribution
ax1 = axes[0, 0]
career_pcts = career_kickers['career_pct']
ax1.hist(career_pcts, bins=20, edgecolor='black', alpha=0.7)
ax1.axvline(x=0.839, color='red', linestyle='--', label='Veteran (83.9%)')
ax1.axvline(x=0.80, color='blue', linestyle='--', label='Avg Replacement')
ax1.set_xlabel('Career FG%')
ax1.set_ylabel('Count')
ax1.set_title('Distribution of Kicker FG%')
ax1.legend()
# Plot 2: Year-to-year scatter
ax2 = axes[0, 1]
# Would need actual paired data
ax2.text(0.5, 0.5, 'Year-to-Year\nCorrelation\nr = 0.35',
ha='center', va='center', fontsize=14, transform=ax2.transAxes)
ax2.set_title('FG% Year-to-Year Stability')
ax2.axis('off')
# Plot 3: Confidence intervals
ax3 = axes[1, 0]
scenarios = ['Last Year\n(27 att)', 'Career\n(186 att)', 'True Range']
lowers = [70, 78, 80]
uppers = [95, 89, 87]
points = [85.2, 83.9, 83.5]
for i, (s, l, u, p) in enumerate(zip(scenarios, lowers, uppers, points)):
ax3.plot([i, i], [l, u], 'b-', linewidth=2)
ax3.plot(i, p, 'ro', markersize=10)
ax3.set_xticks(range(len(scenarios)))
ax3.set_xticklabels(scenarios)
ax3.set_ylabel('FG%')
ax3.set_title('Confidence Intervals for "True" FG%')
ax3.set_ylim(65, 100)
# Plot 4: Value calculation
ax4 = axes[1, 1]
fg_advantages = [0, 2, 5, 10, 15]
point_values = [a * 30 * 3 * 0.01 / 0.3 for a in fg_advantages]
dollar_values = [pv * 80000 for pv in point_values]
ax4.bar(fg_advantages, dollar_values, alpha=0.7)
ax4.axhline(y=4250000, color='red', linestyle='--', label='Salary Difference')
ax4.set_xlabel('FG% Advantage Over Replacement')
ax4.set_ylabel('Dollar Value')
ax4.set_title('Kicker Value by FG% Advantage')
ax4.legend()
ax4.set_ylim(0, 5000000)
plt.tight_layout()
plt.savefig('kicker_value_analysis.png', dpi=300, bbox_inches='tight')
plt.close()
Conclusions
The Analytics Verdict
Sign the rookie.
Evidence:
-
Year-to-year correlation is low (r=0.35): The veteran's past success is weakly predictive of future performance
-
Sample sizes are too small: 27 kicks last year doesn't prove he's better than the rookie
-
Point value difference is tiny: Even a 5% accuracy advantage is worth only ~$400K, not $4.25M
-
FG over expected is modest: The veteran has been slightly above average (+0.4 FG/year) but nothing exceptional
-
Rookie success rates are reasonable: First-year kickers average 80%+, and many improve
Recommendation
| Option | Cost | Expected Value |
|---|---|---|
| Veteran ($5M) | $5,000,000 | ~76.5 points | |
| Rookie ($750K) | $750,000 | ~72 points | |
| Savings | $4,250,000 | -4.5 points |
The expected point difference (~4.5 points) is worth approximately $360K at market rates. Paying $4.25M for that advantage is inefficient by approximately $3.9M.
What the $3.9M Could Buy
- A starting linebacker
- A quality offensive lineman
- Two depth players
- Practice squad and depth overall
Caveats
- Leadership value not captured (veteran experience)
- Clutch performance difficult to quantify (though likely not real)
- Risk of rookie bust exists (but so does veteran decline)
- Pressure situations may favor experience (marginally)
Discussion Questions
-
If the veteran had made 26/27 instead of 23/27, would that change the recommendation?
-
How would you evaluate the "clutch" argument for veterans?
-
At what salary would the veteran be worth signing?
-
Should teams ever pay premium salaries to kickers?
-
How does this analysis apply to other low-volume positions?
Key Lessons
- Small samples deceive: 27 kicks is not enough to evaluate a kicker
- Regression is powerful: Expect kickers to regress toward league average
- Marginal value is small: Even large FG% differences create small point impacts
- Context matters: $5M could improve the team more elsewhere
- Analytics saves money: Data-driven decisions prevent overpaying
Postscript
Historical note: Teams that have signed kickers to large contracts ($4M+) have consistently failed to see returns justifying the investment. The replacement-level baseline for kickers is approximately 82%, and the difference between that and an "elite" kicker (87%) is not worth millions.
The Browns should sign the rookie and invest the savings in positions with higher marginal value.