Then while listening to a recent Pardon My Take episode Big Cat posited the question whether an NFL team could take a Moneyball-esque approach to scoring points by only kicking field goals, maxing out defensive players and running jumbo sets until you make FG range.
This got me thinking if you could solely rely on your kicker to score points? In other words: is it possible to create a winning strategy for an NFL or NCAA football team where the offense immediately kicked field goals once inside a defined range?
I approach the question using an Expected Points (“EP”) model that I am calling the MoneyballFB Model. The challenge is that most EP models rely upon historical averages to relate field position to expected points through statistics. Since most teams in the NFL and NCAAF only kick a field goal on 4th down if they are inside of their kicker’s range (and not as a set strategy) the MoneyballFB model proposed below addresses this and other concerns.
2 Summary of Conclusions
Based on the 2018 to 2024 NFL seasons:
92% of NFL field goal attempts are on 4th down
Overall, after analyzing 35,465 drives
Teams scored a TD 23.4% of the time for 1.64 EP per drive
Teams scored a FG 15.2% of the time for 0.45 EP per drive
NFL teams averaged 2.09 EP per drive
Kicking sooner (the Early FG Model) has lower EP per drive than the Traditional Model across all FG distances, FG make probabilities and starting field positions. In other words, it would never make sense to kick a FG outside of the TM limitations that current NFL teams have adopted.
There is no distance (under current NFL kicking skill levels and rules) where “kick immediately” (the Early FG Model) outperforms the Traditional Model
Although no analysis was made for NCAAF, the conclusion NOT to kick Early FGs would likely be the same
3 Traditional Model
Most teams in the NFL and NCAAF use a Traditional Model (“TM”) for offense. A TM offense will only kick a FG if they have the ball inside the field goal kicker’s range and:
it’s late in downs (4th down),
it’s at the end of either half and there is a limited amount of time to score a TD,
to win or tie a game late in the second half to send the game to overtime (“OT”), or
to win or tie a game in OT.
Traditional arguments for a TM offense are as follows:
Expected Points are higher with a TM offense. Kicking a FG early in the downs means you believe that your offense has a low likelihood of scoring and/or a high likelihood of a turnover by keeping possession.
FG is worth less than half the points of a TD. A FG is worth 3 points and a TD + PAT is worth 7. Thus, you would need to score more than twice as often to make a FG only offense work.
Kicking a FG in early downs results in more possessions and time of possession for the other team. Following a make or miss, the opposing team gets the ball after the FG attempt giving them comparatively more possessions during the game. On a made FG, the scoring team kicks off to the opposing team from the kicking team’s 35-yard line in the NFL and NCAA. If a field goal attempt is unsuccessful, possession of the ball is turned over to the opposing team where the line of scrimmage was on the field goal attempt in the NCAA, or at the spot of the kick (the spot where the placekicker made contact with the ball) in the NFL. More possessions leads to more opponent’s points and a tired defense.
4 Expected Points Modeling Overview
A short overview of Expected Points (“EP”) models is necessary. For more in-depth description, check the Notes & Research section at the end of this report. EP describes how many points, on average, a team is expected to score on a possession given a particular field position. Expected points has also been expanded to quantify the value of a particular field position given:
the down and distance,
time remaining, and
expected points on a turnover.
It is no surprise to the casual football fan to know that being closer to the opponent’s end zone and having more favorable down and distance situations increases EP. For example, consider the following:
Team with 1st & 10 at their own 20 yard line. They have a 0.7 EP.
Team makes a 20 yard completion.
Team now has 1st & 10 at their own 40 yard line with an EP of 2.06
EP modeling connects the two states by showing that the 20 yard throw added 1.36 points.
The scoring possibilities are combined with a probability and modeled so that EP can be calculated. From the cfbfastR site:
For each play, the multinomial logistic regression model calculates what the probability () of each of those scoring outcomes is and the expected points can be calculated by multiplying each of the scoring event probabilities by their associated point values and summing the products, like so:
As noted above, EP models are based on historical averages and do not take into account actions outside of the Traditional Model approach. Asking Bill Belichick to punt on 1st down from the opponent’s 45 yard line is like asking if the Pope poops in his hat. In other words, that shit never happened.
5 The MoneyballFB Model
The MoneyballFB model compares two approaches:
Traditional Model - play normally; kick only on 4th down per historical behavior vs.
Early-FG Model - kick immediately upon entering your defined FG range
It consists of the following components:
Probability of successfully reaching FG range from any given starting field position.
Probability of making the field goal from that range.
For the Traditional Model, you also need the EP from continuing the drive (including touchdowns, missed field goals, turnovers, punts, safeties).
5.1 Steps for the MoneyballFB Model
1. Build a “Drive Progression” Model
Use play-by-play data (e.g., NFLfastR or NCAA equivalents) to model P(next yard line | current yard line, down, distance).
Run Monte Carlo simulations to estimate:
The probability of reaching at least the target FG line from each starting position.
The distribution of field goal distances.
2. Define Field Goal Range & Success Probability
Gather FG attempt data by distance.
Fit a logistic regression (or smoothed curve) of FG make probability vs. distance.
Decide a cut-off (e.g., P(make) ≥ 0.85) as your “range.” This is defined in the code as fg_thresh.
3. Calculate Early FG Model EP
For each starting yard line:
Simulate drive until FG range is reached.
On entry into range, attempt FG immediately.
EP from this approach = P(reach range) × P(FG made) × 3.
4. Calculate Traditional Model EP
Use an existing EP model (yard line → expected points) OR
Simulate traditional play progression with 4th down decision rules.
5. Compare Strategies
Compare EP curves (yard line vs. EP) for both strategies.
Identify:
Break-even yard lines where early FG EP > traditional EP.
Conditions that swing advantage (e.g., bad offense, elite kicker, poor weather, game state).
6. Incorporate Game Context
Variance vs. Mean: Early FG might lower variance (good for underdog or late lead protection).
Clock effects: Kicking early gives opponent the ball sooner.
Defense strength: Strong defense makes low-risk points more attractive.
Special teams quality: Elite kicker, bad punting coverage, etc.
5.2 Data
NFL data was obtained from the nflfastR package from 2018 to 2024. Note: for this post, I did not analyze NCAAF data.
Overall, NFL teams scored more points the closer they started to the endzone. For example, starting at the opponents 40-44 was worth 0.5 EP per drive vs starting at midfield or the 50-54 (3.117 vs 2.642).
FG Percentage Kicked by Down
yl_bin
n_drives
td_rate
fg_rate
avg_td_ep
avg_fg_ep
avg_total_ep
00–04
81
85.2%
8.6%
5.963
0.259
6.222
05–09
122
61.5%
27.9%
4.303
0.836
5.139
10–14
163
55.8%
32.5%
3.908
0.975
4.883
15–19
240
52.1%
38.3%
3.646
1.150
4.796
20–24
419
41.3%
29.1%
2.890
0.874
3.764
25–29
315
44.1%
37.8%
3.089
1.133
4.222
30–34
376
40.7%
35.6%
2.848
1.069
3.918
35–39
16749
22.7%
14.2%
1.590
0.426
2.015
40–44
540
33.3%
26.1%
2.333
0.783
3.117
45–49
604
30.6%
27.3%
2.144
0.820
2.964
50–54
974
29.8%
18.6%
2.084
0.557
2.642
55–59
1143
27.8%
18.3%
1.948
0.549
2.496
60–64
1486
27.9%
18.7%
1.950
0.561
2.511
65–69
1649
22.4%
15.6%
1.571
0.468
2.038
70–74
1707
23.1%
15.1%
1.616
0.453
2.069
75–79
1775
19.6%
13.0%
1.372
0.390
1.763
80–84
2759
17.7%
11.2%
1.238
0.337
1.575
85–89
1809
15.8%
9.8%
1.107
0.294
1.400
90–94
1609
16.0%
9.9%
1.122
0.298
1.421
95–99
945
15.2%
7.3%
1.067
0.219
1.286
All Starts
35465
23.4%
15.2%
1.639
0.455
2.094
5.3 Issues with EP Models
EP models are all based on a TM offense that generally won’t kick a field goal in early downs. Since no team that I could find in the NFL or NCAAF regularly kicked FGs early in downs, EP models don’t integrate the early FG approach. Based on the NFL data, almost 92% of the field goal attempts are on 4th down.
EP per Drive by Start Bin + League-wide Overall
down
fg_attempts
percentage
1
186
0.0251998
2
221
0.0299417
3
205
0.0277740
4
6769
0.9170844
5.4 Specifics of the Early FG Model
Initially, I used yardline_100 as my location variable, and restricted the analysis to non-garbage and non-overtime situations. Quick note: yardline_100 is the number of yards from the line of scrimmage to the opponent’s end zone. It’s always from the offense’s perspective, so:
100 = your own goal line (i.e., you’re about to snap from your own 0-yard line).
50 = midfield.
1 = the opponent’s 1-yard line (almost a TD).
0 = the opponent’s end zone (used for scoring plays).
Field goal distance was estimated using the kick_dist variable. In the NFL, the distance of a field goal is equal to the line of scrimmage plus the length of the endzone (10 yards) plus where the hold occurs (estimated at 7 yards). So a total of 17 yards was added to the line of scrimmage.
As a baseline, kick distance is modeled as a smoothed logistic where only the kick distance predicts the make probability.
Then, the minimum make percentage is input using the fg_thresh parameter. This is the minimum probability (based on the logistic model) where a FG could be made a required minimum percentage of the time. If fg_thresh is set to 0.85, kickers in the model must make at a minimum 85% of their kicks from that distance. The Early FG model then is “in range” when the the yards to the opponent’s endzone is within the models required minimum range (yardline_100 ≤ fg_range_min_yd100) and a field goal is kicked. For example, if the the user sets a minimum threshold of 85% makes and that is at the opponents 33 yard line, then once the Early FG model is within this range, the kick is attempted regardless of the down or time remaining in the game.
I used a coarse Markov model for “normal plays” by (down, to-go bucket, yardline bucket) that captures where the offense ends up next and which terminal events occur (TD, INT, FUM, SACK-fumble leading to turnover, Punt, FG attempt, TO on downs, etc.) to end the drive. For the Traditional Model, historical 4th-down behavior governs (i.e., the transition matrix already embeds real-world kick/punt/go for it behavior). That keeps the baseline honest without hard-coding any specific 4th-down model.
Then, compute an EP curve directly from nflfastR’s ep column. For simulation we’ll just roll forward with the empirical transitions until we hit a terminal outcome and assign points:
TD = +7 (the extra point was assumed)
FG_ATT is not guaranteed points; if historical next play is FG_ATT, we’ll simulate the FG using our kicker model (instead of blindly awarding 3).
PUNT, TURNOVER, SAFETY → opponent ball; here we’ll stop the drive and return 0 points for the offense. (This can be expanded to opponent-return EP later.)
Next, simulate the Early FG model. As discussed above, the instant the Team crosses into FG range (yardline_100 ≤ fg_range_min_yd100), attempt a FG. Note: I still use the same empirical transitions to “walk” the ball down the field prior to the fg_thresh, but I intercept when FG range is reached. This means that prior to the early FG zone, both models should look the same.
Finally, I ran head to head simulations calculating the expected points per drive (“EPD”) from a set of starting yard lines for both the Traditional Model and the Early FG Model. Simulations for each model were repeated 10,000 times and the results were compared.
6 Model Outputs
Much of the comparisons between the Early FG model and the Traditional Model are dependent on the minimum FG make probability.
The chart below shows the model and the actual historical FG make probability vs field position. For example, if you set a 50% make probability, then you need to kick from the Opponent’s 40 yard line of closer.
The second chart relates the make probability to the kick distance rather than field positon. Recall that since 17 yards are added for the holder and end zone to the line of scrimmage, a 50% probability of a make equates to a kick distance of 57 yards.
6.1 85% FG Percentage Threshhold
At fg_thresh = 0.85, the range starts around the opponent’s 22 yard line. From those distances, the Traditional Model drives still have a solid chance to convert first downs and push for a TD. This simulation compared kicking a FG immediately once the ball was within range vs the Traditional Model.
85% FG Percentage Threshhold
yardline_100
EP_traditional
EP_earlyFG
EP_diff
pr_3plus_trad
pr_3plus_early
85
3.9605
2.5235
-1.4370
0.5675
0.6725
80
3.9170
2.6570
-1.2600
0.5610
0.6970
75
4.1485
2.5320
-1.6165
0.5935
0.6800
70
4.1500
2.6035
-1.5465
0.5940
0.6865
65
4.0475
2.6085
-1.4390
0.5785
0.6975
60
4.2030
2.5725
-1.6305
0.6020
0.6995
55
4.2825
2.6300
-1.6525
0.6125
0.7120
50
4.3965
2.7085
-1.6880
0.6285
0.7475
45
4.5255
2.7470
-1.7785
0.6475
0.7510
40
4.5910
2.8955
-1.6955
0.6570
0.7885
35
4.8625
2.8915
-1.9710
0.6955
0.8105
30
4.7720
2.8780
-1.8940
0.6820
0.8360
25
5.1495
3.0060
-2.1435
0.7365
0.8800
The table output columns are defined as follows:
yardline_100 Starting field position (yards from opponent’s end zone). Lower numbers = better starting position.
EP_traditional Expected points per drive using historical 4th-down behavior (drive until stopped or choose FG).
EP_earlyFG Expected points if you kick as soon as you’re within range for an ~85% make rate.
EP_diffEP_earlyFG − EP_traditional. Negative means the early FG strategy is worse.
pr_3plus_trad Probability the drive ends with ≥ 3 points under Traditional Model.
pr_3plus_early Same probability under Early FG Model strategy.
6.2 75% FG Percentage Threshhold
75% FG Percentage Threshhold
yardline_100
EP_traditional
EP_earlyFG
EP_diff
pr_3plus_trad
pr_3plus_early
85
4.0385
2.6250
-1.4135
0.5785
0.6930
80
4.2040
2.5775
-1.6265
0.6010
0.6925
75
4.1145
2.5520
-1.5625
0.5885
0.6780
70
4.0300
2.5550
-1.4750
0.5770
0.6950
65
4.1515
2.6355
-1.5160
0.5935
0.6945
60
4.3440
2.6785
-1.6655
0.6220
0.7115
55
4.2570
2.7180
-1.5390
0.6090
0.7360
50
4.3705
2.7240
-1.6465
0.6255
0.7460
45
4.4015
2.7120
-1.6895
0.6305
0.7380
40
4.5100
2.8145
-1.6955
0.6450
0.7975
35
4.8275
2.8380
-1.9895
0.6905
0.8080
30
5.0190
2.9915
-2.0275
0.7170
0.8545
25
5.0940
3.0180
-2.0760
0.7280
0.9020
6.3 65% FG Percentage Threshhold
65% FG Percentage Threshhold
yardline_100
EP_traditional
EP_earlyFG
EP_diff
pr_3plus_trad
pr_3plus_early
85
3.8595
2.5915
-1.2680
0.5525
0.6965
80
3.8760
2.4630
-1.4130
0.5550
0.6590
75
4.2010
2.5565
-1.6445
0.6010
0.6915
70
3.9690
2.5760
-1.3930
0.5690
0.6920
65
4.1435
2.6400
-1.5035
0.5925
0.7160
60
4.2310
2.6120
-1.6190
0.6050
0.7080
55
4.4840
2.7045
-1.7795
0.6410
0.7335
50
4.3200
2.6985
-1.6215
0.6180
0.7415
45
4.3375
2.7810
-1.5565
0.6205
0.7610
40
4.5260
2.8415
-1.6845
0.6480
0.7845
35
4.7970
2.8825
-1.9145
0.6860
0.8155
30
4.7830
2.8875
-1.8955
0.6840
0.8285
25
4.9675
3.0685
-1.8990
0.7105
0.8915
6.4 50% FG Percentage Threshhold
50% FG Percentage Threshhold
yardline_100
EP_traditional
EP_earlyFG
EP_diff
pr_3plus_trad
pr_3plus_early
85
4.0225
2.5815
-1.4410
0.5765
0.6645
80
4.0560
2.5760
-1.4800
0.5800
0.6820
75
4.1775
2.6405
-1.5370
0.5975
0.6875
70
4.0895
2.5535
-1.5360
0.5855
0.6905
65
4.1300
2.5645
-1.5655
0.5910
0.6955
60
4.2970
2.5900
-1.7070
0.6150
0.7060
55
4.2460
2.6705
-1.5755
0.6080
0.7295
50
4.4515
2.6860
-1.7655
0.6365
0.7380
45
4.3155
2.7425
-1.5730
0.6175
0.7555
40
4.6265
2.8590
-1.7675
0.6615
0.7870
35
4.6960
2.8900
-1.8060
0.6720
0.8120
30
4.9795
2.9175
-2.0620
0.7125
0.8305
25
5.0930
2.9860
-2.1070
0.7280
0.8880
7 Minimum FG Make Probability
Having struck out defining specific fg_thresh values, I decided to expand my view to see if the Early FG model ever had higher EP than a Traditional Model. The steps were as below:
Pick a grid of fg_thresh values (0.50 → 0.95).
Pick a set of starting yardline_100 values which is the Team starting field position (30 → 75)
Run both the Traditional Model and Early FG strategies for each combination.
Find the cross-over point (where EP_diff is positive).
Plot the curves an tables so you can visually see where the Early FG starts to make sense.
8 Break-Even Calculation for Early FG Model
To this point, I failed to find a situation where the Early FG model beat the Traditional Model when comparing points per drive. My initial explanation was that NFL kickers aren’t making a high engough percentage of field goals from far enough away. Which brings me to the last question: what make percentage would be required for the Early FG model to break even versus the TM model from a given start, if we commit to kicking at a particular field position? How good would a kicker have to be to make the MoneyballFB Model better than the existing NFL approach?
Turns out there is no real limit.
The plot below is showing that for every tested starting field position and kick distance, the minimum make percentage required for “Early FG” ≥ “Traditional EP” is 100%.
That means:
Even if a kicker could make every single attempt from those distances, the Early FG strategy just barely ties the traditional strategy at best.
At any real-world FG% (<100%), Early FG is always worse in terms of expected points.
The reason is that a traditional drive has upside beyond 3 points (touchdowns, closer FGs, penalties in your favor), while Early FG caps you at 3 points and adds the risk of a miss.
Thus, there is no distance (under current NFL kicking skill levels and rules) where “kick immediately” outperforms the traditional strategy.
Break-even make% for Early-FG to exceed Traditional EP (by start & distance)
start_yl100
kick_dist
frontier_yl100
EP_traditional
pi_reach
p_required_pct
p_model_pct
EP_diff_model
EP_positive
85
45
28
3.500
0.800
100.0%
75.5%
-1.688
NO
85
50
33
3.500
0.767
100.0%
70.6%
-1.876
NO
65
55
38
3.733
0.867
100.0%
62.3%
-2.113
NO
75
50
33
3.900
0.833
100.0%
70.6%
-2.135
NO
65
45
28
3.733
0.700
100.0%
75.5%
-2.148
NO
55
50
33
3.967
0.833
100.0%
70.6%
-2.201
NO
75
45
28
3.900
0.733
100.0%
75.5%
-2.239
NO
85
55
38
3.500
0.667
100.0%
62.3%
-2.253
NO
65
50
33
3.733
0.667
100.0%
70.6%
-2.321
NO
85
60
43
3.500
0.767
100.0%
43.6%
-2.497
NO
55
45
28
3.967
0.633
100.0%
75.5%
-2.532
NO
55
55
38
3.967
0.733
100.0%
62.3%
-2.595
NO
65
60
43
3.733
0.867
100.0%
43.6%
-2.600
NO
95
55
38
4.133
0.800
100.0%
62.3%
-2.637
NO
95
50
33
4.133
0.700
100.0%
70.6%
-2.650
NO
75
55
38
3.900
0.633
100.0%
62.3%
-2.716
NO
95
45
28
4.133
0.567
100.0%
75.5%
-2.850
NO
75
60
43
3.900
0.800
100.0%
43.6%
-2.853
NO
45
45
28
4.900
0.867
100.0%
75.5%
-2.937
NO
95
60
43
4.133
0.900
100.0%
43.6%
-2.956
NO
85
65
48
3.500
0.700
100.0%
23.5%
-3.006
NO
55
60
43
3.967
0.733
100.0%
43.6%
-3.007
NO
65
65
48
3.733
0.967
100.0%
23.5%
-3.051
NO
85
100
83
3.500
1.000
100.0%
11.3%
-3.161
NO
85
75
58
3.500
0.933
100.0%
11.3%
-3.184
NO
85
85
68
3.500
0.933
100.0%
11.3%
-3.184
NO
85
95
78
3.500
0.933
100.0%
11.3%
-3.184
NO
85
70
53
3.500
0.900
100.0%
11.3%
-3.195
NO
75
65
48
3.900
0.900
100.0%
23.5%
-3.265
NO
45
50
33
4.900
0.767
100.0%
70.6%
-3.276
NO
45
55
38
4.900
0.867
100.0%
62.3%
-3.279
NO
55
65
48
3.967
0.867
100.0%
23.5%
-3.355
NO
65
85
68
3.733
1.000
100.0%
11.3%
-3.395
NO
65
95
78
3.733
1.000
100.0%
11.3%
-3.395
NO
65
100
83
3.733
1.000
100.0%
11.3%
-3.395
NO
65
75
58
3.733
0.967
100.0%
11.3%
-3.406
NO
65
70
53
3.733
0.900
100.0%
11.3%
-3.428
NO
75
95
78
3.900
1.000
100.0%
11.3%
-3.561
NO
75
100
83
3.900
1.000
100.0%
11.3%
-3.561
NO
45
60
43
4.900
1.000
100.0%
43.6%
-3.592
NO
95
65
48
4.133
0.767
100.0%
23.5%
-3.592
NO
75
75
58
3.900
0.900
100.0%
11.3%
-3.595
NO
75
85
68
3.900
0.867
100.0%
11.3%
-3.606
NO
55
70
53
3.967
1.000
100.0%
11.3%
-3.628
NO
55
75
58
3.967
1.000
100.0%
11.3%
-3.628
NO
55
85
68
3.967
1.000
100.0%
11.3%
-3.628
NO
55
95
78
3.967
1.000
100.0%
11.3%
-3.628
NO
55
100
83
3.967
1.000
100.0%
11.3%
-3.628
NO
75
70
53
3.900
0.800
100.0%
11.3%
-3.629
NO
95
85
68
4.133
0.967
100.0%
11.3%
-3.806
NO
95
100
83
4.133
0.933
100.0%
11.3%
-3.817
NO
95
75
58
4.133
0.900
100.0%
11.3%
-3.828
NO
95
95
78
4.133
0.867
100.0%
11.3%
-3.840
NO
95
70
53
4.133
0.833
100.0%
11.3%
-3.851
NO
45
65
48
4.900
1.000
100.0%
23.5%
-4.194
NO
45
70
53
4.900
1.000
100.0%
11.3%
-4.561
NO
45
75
58
4.900
1.000
100.0%
11.3%
-4.561
NO
45
85
68
4.900
1.000
100.0%
11.3%
-4.561
NO
45
95
78
4.900
1.000
100.0%
11.3%
-4.561
NO
45
100
83
4.900
1.000
100.0%
11.3%
-4.561
NO
9 Notes & Research
american football - Could an immediate field goal be advantageous in NFL? - Sports Stack Exchange - Link - DB
Field Goal Success Probabilities by Direction | NFL Football Operations - Link - DB
Visualizing NFL Kicker Accuracy Trends (1999-2024) // Conor McLaughlin - Link - DB
Using Ridgeline Plots to Visualize the NFL’s Shift Towards Longer Field Goal Attempts // Conor McLaughlin - Link - DB
College Football Expected Points Model Fundamentals - Part I • cfbfastR - Link - DB
College Football Expected Points Model Fundamentals - Part II • cfbfastR - Link - DB
College Football Expected Points Model Fundamentals - Part III • cfbfastR - Link - DB
What is the Predicted Probability of a Field Goal Given Yards - Link - DB
Advanced Football Analytics (formerly Advanced NFL Stats): Expected Point Values - Link - DB
An R package to quickly obtain clean and tidy NFL play by play data • nflfastR - Link - DB
Source Code
---title: "Moneyball for Football: Early FGs For the Win"author: "Jake Stoetzner"date: 2025-08-13format: html: theme: flatly # Bootstrap theme toc: true # Table of contents toc-depth: 3 fig-cap-location: top # Place captions above or below code-fold: false # Don't fold code unless you want to self-contained: true # Embed everything into a single HTML file smooth-scroll: true df-print: paged # Paginate large data frames number-sections: true # Number sections in TOC code-overflow: wrap # Wrap long code lines code-tools: true # Show copy/download buttons for codeexecute: echo: false # Hide code by default warning: false # Suppress warnings in output message: false # Suppress messages in output cache: true # Cache computationseditor: source # Quarto visual editor - source or visual---```{r load required packages}#| output: false# install.packages(c("nflfastR","tidyverse","data.table","arrow","mgcv","yardstick"))library(tidyverse)library(lubridate)library(data.table)library(knitr)library(kableExtra)library(nflfastR)library(tidyverse)library(mgcv)library(yardstick)set.seed(42)``````{r custom functions}```## IntroductionRecently an [NFL kicker made a 70 yard field goa](https://www.youtube.com/watch?v=2Ofwp5x65C0)l in a preseason game. This is a reflection of a larger trend that teams are (1) [attempting](https://conormclaughlin.net/2025/01/using-ridgeline-plots-to-visualize-the-nfls-shift-towards-longer-field-goal-attempts/) more "long" field goals and fewer "short" field goals, and (2) [making](https://conormclaughlin.net/2025/01/visualizing-nfl-kicker-accuracy-trends-1999-2024/) these longer field goals at a higher rate. Additionally, for the 2025-26 season, the [NFL is allowing teams to break-in "K-Balls"](https://operations.nfl.com/updates/the-rules/approved-2025-playing-rules-bylaws-and-resolutions/) (kicking balls) before game day which may result in even [longer FG attempts this year](https://www.reddit.com/r/nfl/comments/1mmo3n5/cam_littles_improbable_70yard_kick_for_jaguars_is/).Then while listening to a recent [Pardon My Take episode](https://youtu.be/E5ayJ_I4Lro) Big Cat posited the [question](https://youtube.com/clip/Ugkxzpox1dV0FZVm3v52DyV01La-czvzch7r?si=v63WIM9ZYePE89tG) whether an NFL team could take a [Moneyball-esque](https://en.wikipedia.org/wiki/Moneyball_(film)) approach to scoring points by only kicking field goals, maxing out defensive players and running jumbo sets until you make FG range.```{=html}<iframe width="560" height="315" src="https://www.youtube.com/embed/E5ayJ_I4Lro?si=v63WIM9ZYePE89tG&clip=Ugkxzpox1dV0FZVm3v52DyV01La-czvzch7r&clipt=EJD_Qxjw00c" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>```This got me thinking if you could *solely* rely on your kicker to score points? In other words: **is it possible to create a winning strategy for an NFL or NCAA football team where the offense immediately kicked field goals once inside a defined range?**I approach the question using an [Expected Points](https://www.sportsinfosolutions.com/2025/08/13/a-new-expected-points-model/) ("EP") model that I am calling the MoneyballFB Model. The challenge is that most EP models rely upon historical averages to relate field position to expected points through statistics. Since most teams in the NFL and NCAAF only kick a field goal on 4th down if they are inside of their kicker's range (and not as a set strategy) the MoneyballFB model proposed below addresses this and other concerns.## Summary of ConclusionsBased on the 2018 to 2024 NFL seasons:- 92% of NFL field goal attempts are on 4th down- Overall, after analyzing 35,465 drives - Teams scored a TD 23.4% of the time for 1.64 EP per drive - Teams scored a FG 15.2% of the time for 0.45 EP per drive - NFL teams averaged 2.09 EP per drive- Kicking sooner (the Early FG Model) has lower EP per drive than the Traditional Modelacross all FG distances, FG make probabilities and starting field positions. In other words, it would never make sense to kick a FG outside of the TM limitations that current NFL teams have adopted.- There is no distance (under current NFL kicking skill levels and rules) where “kick immediately” (the Early FG Model) outperforms the Traditional Model- Although no analysis was made for NCAAF, the conclusion NOT to kick Early FGs would likely be the same## Traditional ModelMost teams in the NFL and NCAAF use a Traditional Model ("TM") for offense. A TM offense will only kick a FG if they have the ball inside the field goal kicker's range **and**:- it's late in downs (4th down),- it's at the end of either half and there is a limited amount of time to score a TD,- to win or tie a game late in the second half to send the game to overtime ("OT"), or- to win or tie a game in OT.Traditional arguments for a TM offense are as follows:- **Expected Points are higher with a TM offense.** Kicking a FG early in the downs means you believe that your offense has a low likelihood of scoring and/or a high likelihood of a turnover by keeping possession.- **FG is worth less than half the points of a TD.** A FG is worth 3 points and a TD + PAT is worth 7. Thus, you would need to score more than twice as often to make a FG only offense work.- **Kicking a FG in early downs results in more possessions and time of possession for the other team.** Following a make or miss, the opposing team gets the ball after the FG attempt giving them comparatively more possessions during the game. On a made FG, the scoring team kicks off to the opposing team from the kicking team's 35-yard line in the NFL and NCAA. If a field goal attempt is unsuccessful, possession of the ball is turned over to the opposing team where the line of scrimmage was on the field goal attempt in the NCAA, or at the spot of the kick (the spot where the placekicker made contact with the ball) in the NFL. More possessions leads to more opponent's points and a tired defense.## Expected Points Modeling OverviewA short overview of Expected Points ("EP") models is necessary. For more in-depth description, check the Notes & Research section at the end of this report. EP describes how many points, on average, a team is expected to score on a possession given a particular field position. Expected points has also been expanded to quantify the value of a particular field position given:- the down and distance,- time remaining, and- expected points on a turnover.It is no surprise to the casual football fan to know that being closer to the opponent's end zone and having more favorable down and distance situations increases EP. [For example](https://www.nfeloapp.com/analysis/expected-points-added-epa-nfl/), consider the following:- Team with 1st & 10 at their own 20 yard line. They have a 0.7 EP.- Team makes a 20 yard completion.- Team now has 1st & 10 at their own 40 yard line with an EP of 2.06- EP modeling connects the two states by showing that the 20 yard throw added 1.36 points.[Source: What are Expected Points Added (EPA) in the NFL?](https://www.nfeloapp.com/_next/image/?url=https%3A%2F%2Fimages.ctfassets.net%2Fwwdoup4surp5%2F3cBQ6fY4WA4DVfgQxrmwEP%2F2b569630da2b4f30f07dc0415ae72eca%2FScreen_Shot_2021-10-23_at_5.24.03_PM.png&w=1920&q=75)Here is a chart of EP by field position from [Advanced Football Analytics](https://www.advancedfootballanalytics.com/2009/12/expected-point-values.html) for NFL teams:Expected points for NCAAF by field position from the [`cfbfastR` site](https://cfbfastr.sportsdataverse.org/articles/college-football-expected-points-model-fundamentals-part-i.html):EP can also be used to find the [relative values of downs at particular points on the field](https://www.nfeloapp.com/analysis/expected-points-added-epa-nfl/) or incorporate down and distance with field position to determine EP. Interesting comparisons arise like "[1st and 10 is worth about the same as 2nd and 2 for just about every position on the field](https://www.nfeloapp.com/analysis/expected-points-added-epa-nfl/)."[EP Models](https://cfbfastr.sportsdataverse.org/articles/college-football-expected-points-model-fundamentals-part-i.html) generally have 7 scoring possibilities:- Touchdown (7)- Field Goal (3)- Safety (2)- No Score (0)- Opponent Safety (-2)- Opponent Field Goal (-3)- Opponent Touchdown (-7)The scoring possibilities are combined with a probability and modeled so that EP can be calculated. From the [`cfbfastR` site](https://cfbfastr.sportsdataverse.org/articles/college-football-expected-points-model-fundamentals-part-i.html):> For each play, the multinomial logistic regression model calculates what the probability () of each of those scoring outcomes is and the expected points can be calculated by multiplying each of the scoring event probabilities by their associated point values and summing the products, like so:As noted above, EP models are based on historical averages and do not take into account actions outside of the Traditional Model approach. Asking Bill Belichick to punt on 1st down from the opponent's 45 yard line is like asking if the Pope poops in his hat. In other words, that shit never happened.## The MoneyballFB ModelThe MoneyballFB model compares two approaches:1. **Traditional Model** - play normally; kick only on 4th down per historical behavior vs.2. **Early-FG Model** - kick immediately upon entering your defined FG rangeIt consists of the following components:- **Probability of successfully reaching FG range from any given starting field position.**- **Probability of making the field goal from that range.**- For the Traditional Model, you also need the **EP from continuing the drive** (including touchdowns, missed field goals, turnovers, punts, safeties).### Steps for the MoneyballFB Model**1. Build a "Drive Progression" Model**- Use play-by-play data (e.g., NFLfastR or NCAA equivalents) to model **P(next yard line \| current yard line, down, distance)**.- Run Monte Carlo simulations to estimate: - The probability of reaching at least the target FG line from each starting position. - The distribution of field goal distances.**2. Define Field Goal Range & Success Probability**- Gather FG attempt data by distance.- Fit a logistic regression (or smoothed curve) of **FG make probability vs. distance**.- Decide a cut-off (e.g., P(make) ≥ 0.85) as your "range." This is defined in the code as `fg_thresh`.**3. Calculate Early FG Model EP**For each starting yard line:1. **Simulate drive until FG range is reached**.2. On entry into range, **attempt FG immediately**.3. EP from this approach = P(reach range) × P(FG made) × 3.**4. Calculate Traditional Model EP**- Use an existing EP model (yard line → expected points) *OR*- Simulate traditional play progression with 4th down decision rules.**5. Compare Strategies**- Compare EP curves (yard line vs. EP) for both strategies.- Identify: - Break-even yard lines where early FG EP \> traditional EP. - Conditions that swing advantage (e.g., bad offense, elite kicker, poor weather, game state).**6. Incorporate Game Context**- **Variance vs. Mean**: Early FG might lower variance (good for underdog or late lead protection).- **Clock effects**: Kicking early gives opponent the ball sooner.- **Defense strength**: Strong defense makes low-risk points more attractive.- **Special teams quality**: Elite kicker, bad punting coverage, etc.### DataNFL data was obtained from the [`nflfastR` package](https://www.nflfastr.com/) from 2018 to 2024. Note: for this post, I did not analyze NCAAF data.```{r data prep helper}#| echo: false#| output: falseseasons <-2018:2024# adjust as you like# pbp <- load_pbp(seasons)# write_csv(pbp, file = "/Users/jstoetz/Library/CloudStorage/Dropbox/@projects/p2025-09-moneyball-football/nfl-2018-2024.csv")pbp <-read_csv("/Users/jstoetz/Library/CloudStorage/Dropbox/@projects/p2025-09-moneyball-football/nfl-2018-2024.csv")clean_pbp <- pbp %>%filter(!is.na(yardline_100),!is.na(down),!is.na(ydstogo), qtr <=4, # ignore OT for first pass!is.na(play_type)) %>%mutate(# standard field goal distance approximationkick_dist =if_else(play_type =="field_goal", yardline_100 +17, NA_real_),# binning for empirical transitionsyl_bin =pmin(20L, pmax(0L, floor((100- yardline_100)/5))) # 0=own goal line … 20=opp EZ )```Overall, NFL teams scored more points the closer they started to the endzone. For example, starting at the opponents 40-44 was worth 0.5 EP per drive vs starting at midfield or the 50-54 (3.117 vs 2.642).```{r actual outcomes}# 1) Build drive-level outcomes directly from plays# - start_yl100: yardline_100 on the first offensive snap of the drive# - td_for: did the offense that started the drive score a TD on this drive?# - fg_for: did they make a FG on this drive?drives <- pbp %>%filter(qtr <=4) %>%# (optional) regulation onlygroup_by(game_id, drive) %>%summarise(start_yl100 = { x <- yardline_100[!is.na(yardline_100)]if (length(x)) x[1] elseNA_real_ },drive_offense = dplyr::first(posteam), # posteam is constant within a drivetd_for =any(touchdown ==1&if ("td_team"%in%names(cur_data_all())) td_team == dplyr::first(posteam) # prefer td_team when availableelseTRUE), # otherwise touchdown==1 is for the offensefg_for =any(play_type =="field_goal"& field_goal_result =="made"& posteam == dplyr::first(posteam)),.groups ="drop" ) %>%filter(!is.na(start_yl100))# 2) Compute EP components per drivedrives <- drives %>%mutate(ep_td =if_else(td_for, 7, 0),ep_fg =if_else(!td_for & fg_for, 3, 0), # mutually exclusive on a driveep_total = ep_td + ep_fg )# 3) Bin by 5-yard increments of the starting field positiondrive_ep_binned <- drives %>%na.omit() %>%mutate(yl_bin =cut( start_yl100,breaks =seq(0, 100, by =5),include.lowest =TRUE,right =FALSE,labels =sprintf("%02d–%02d", seq(0,95,5), seq(4,99,5)) )) %>%group_by(yl_bin) %>%summarise(n_drives =n(),td_rate =mean(td_for),fg_rate =mean(fg_for),avg_td_ep =mean(ep_td), # expected points from TDs onlyavg_fg_ep =mean(ep_fg), # expected points from made FGs onlyavg_total_ep =mean(ep_total),.groups ="drop" ) %>%arrange(as.numeric(sub("–.*", "", yl_bin))) # sort by bin start# 4) Pretty table#drive_ep_binned %>%# mutate(# td_rate = scales::percent(td_rate, 0.1),# fg_rate = scales::percent(fg_rate, 0.1),# across(starts_with("avg_"), ~round(.x, 3))# ) %>%# knitr::kable(# align = "c",# caption = "League-wide EP per Drive by Starting Field Position (TD vs FG components)"# )# Overall (drive-weighted) league averages across ALL startsoverall_ep <- drives %>%na.omit() %>%summarise(n_drives =n(),td_rate =mean(ep_td >0), # proportion of drives that scored a TDfg_rate =mean(ep_fg >0), # proportion of drives that scored a (made) FGavg_td_ep =mean(ep_td), # expected points from TDs onlyavg_fg_ep =mean(ep_fg), # expected points from FGs onlyavg_total_ep =mean(ep_total) # TD + FG (by design here) )#overall_epoverall_row <- overall_ep %>%mutate(yl_bin ="All Starts") %>%select(yl_bin, n_drives, td_rate, fg_rate, avg_td_ep, avg_fg_ep, avg_total_ep)drive_ep_binned_with_overall <- drive_ep_binned %>%bind_rows(overall_row) %>%mutate(td_rate = scales::percent(td_rate, 0.1),fg_rate = scales::percent(fg_rate, 0.1),across(starts_with("avg_"), ~round(.x, 3)) )knitr::kable(drive_ep_binned_with_overall, align ="c",caption ="FG Percentage Kicked by Down") %>% kableExtra::kable_classic(full_width =FALSE)```### Issues with EP ModelsEP models are all based on a TM offense that generally won't kick a field goal in early downs. Since no team that I could find in the NFL or NCAAF regularly kicked FGs early in downs, EP models don't integrate the early FG approach. Based on the NFL data, almost 92% of the field goal attempts are on 4th down.```{r issues with EP models}fg_by_down <- clean_pbp %>%filter(play_type =="field_goal") %>%group_by(down) %>%summarise(fg_attempts =n(),.groups ="drop" ) %>%arrange(down)knitr::kable(fg_by_down %>%mutate(percentage = fg_attempts /sum(fg_attempts)), align ="c",caption ="EP per Drive by Start Bin + League-wide Overall") %>% kableExtra::kable_classic(full_width =FALSE)```### Specifics of the Early FG ModelInitially, I used `yardline_100` as my location variable, and restricted the analysis to non-garbage and non-overtime situations. Quick note: `yardline_100` is the number of yards from the line of scrimmage to the opponent's end zone. It's always from the offense's perspective, so:- *100* = your own goal line (i.e., you're about to snap from your own 0-yard line).- *50* = midfield.- *1* = the opponent's 1-yard line (almost a TD).- *0* = the opponent's end zone (used for scoring plays).Field goal distance was estimated using the `kick_dist` variable. In the NFL, the distance of a field goal is equal to the line of scrimmage plus the length of the endzone (10 yards) plus where the hold occurs (estimated at 7 yards). So a total of 17 yards was added to the line of scrimmage.As a baseline, kick distance is modeled as a smoothed logistic where *only* the kick distance predicts the make probability.Then, the minimum make percentage is input using the `fg_thresh` parameter. This is the minimum probability (based on the logistic model) where a FG could be made a required minimum percentage of the time. If `fg_thresh` is set to 0.85, kickers in the model must make at a minimum 85% of their kicks from that distance. The Early FG model then is "in range" when the the yards to the opponent's endzone is within the models required minimum range (`yardline_100` ≤ `fg_range_min_yd100`) and a field goal is kicked. For example, if the the user sets a minimum threshold of 85% makes and that is at the opponents 33 yard line, then once the Early FG model is within this range, the kick is attempted regardless of the down or time remaining in the game.```{r Field-goal make model}fg_data <- clean_pbp %>%filter(play_type =="field_goal",!is.na(field_goal_result),!is.na(kick_dist)) %>%mutate(make =as.integer(field_goal_result =="made"))# Smooth logistic via GAMfg_mod <-gam(make ~s(kick_dist, k =8), data = fg_data, family =binomial())p_make_fg <-function(kick_dist) { kick_dist <-pmin(pmax(kick_dist, 15), 70) # clamp reasonable boundsas.numeric(predict(fg_mod, newdata =data.frame(kick_dist), type ="response"))}fg_thresh <-0.85# configurableyd100_grid <-1:80mk <-p_make_fg(yd100_grid +17)fg_range_min_yd100 <-max(yd100_grid[mk >= fg_thresh], na.rm =TRUE)```I used a coarse [Markov model](https://en.wikipedia.org/wiki/Markov_decision_process) for "normal plays" by (down, to-go bucket, yardline bucket) that captures where the offense ends up next and which terminal events occur (TD, INT, FUM, SACK-fumble leading to turnover, Punt, FG attempt, TO on downs, etc.) to end the drive. For the Traditional Model, historical 4th-down behavior governs (i.e., the transition matrix already embeds real-world kick/punt/go for it behavior). That keeps the baseline honest without hard-coding any specific 4th-down model.```{r Empirical play progression transitions}# Bucket to-go and yardline:binner <-function(x, cuts) pmin(length(cuts)-1L, pmax(1L, as.integer(cut(x, breaks = cuts, labels =FALSE, include.lowest =TRUE))))togo_cuts <-c(0, 2, 4, 6, 8, 10, 15, 99) # bucketsyl_cuts <-c(seq(0, 100, by =5)) # 5-yard increments# Identify "next state" after the play:# We'll map the next snap state (or terminal outcome) from nflfastR columns.trans_df <- clean_pbp %>%# Exclude pure special-teams and penalties for the progression matrix; we keep FGs to learn range but remove them herefilter(play_type %in%c("run","pass")) %>%mutate(togo_bin =binner(ydstogo, togo_cuts),yl_next =lead(yardline_100), # next snap location (from nflfastR sequence)down_next =lead(down),togo_next =lead(ydstogo),# terminal outcomestd_for = touchdown,turnover =if_else(interception ==1| fumble_lost ==1, 1L, 0L),to_on_downs =if_else(first_down ==0& play_type %in%c("run","pass") & (down ==4), 1L, 0L),punt_next =as.integer(lead(play_type) =="punt"),fg_next =as.integer(lead(play_type) =="field_goal"),# score events on *this* play:td_now =as.integer(touchdown ==1),safety_now =as.integer(safety ==1) ) %>%filter(!is.na(togo_bin), !is.na(yl_bin))# Build empirical transition choices:# We record either: (A) terminal outcome, or (B) a continued state (down_next, togo_next, yl_next).transitions <- trans_df %>%mutate(terminal =case_when( td_now ==1~"TD", safety_now ==1~"SAFETY", turnover ==1~"TURNOVER", to_on_downs ==1~"TODS", punt_next ==1~"PUNT", fg_next ==1~"FG_ATT",TRUE~"NONE" ),continue = terminal =="NONE" ) %>%filter(!(is.na(yl_next) & continue)) %>%mutate(togo_bin_next =if_else(continue, binner(togo_next, togo_cuts), NA_integer_),yl_bin_next =if_else(continue, pmin(20L, pmax(0L, floor((100- yl_next)/5))), NA_integer_),down_next2 =if_else(continue, down_next, NA_real_) ) %>%group_by(down, togo_bin, yl_bin) %>%summarise(n =n(),# Probabilities of terminal outcomesp_TD =mean(terminal =="TD"),p_SAF =mean(terminal =="SAFETY"),p_TOV =mean(terminal =="TURNOVER"),p_TODS =mean(terminal =="TODS"),p_PUNT =mean(terminal =="PUNT"),p_FG =mean(terminal =="FG_ATT"),# Distribution over continued next statesnext_states =list(tibble(down_next = down_next2[continue],togo_bin_next = togo_bin_next[continue],yl_bin_next = yl_bin_next[continue] )) ) %>%ungroup()sample_next <-function(down, togo_bin, yl_bin) { row <- transitions %>%filter(down ==!!down, togo_bin ==!!togo_bin, yl_bin ==!!yl_bin)if (nrow(row) ==0) return(list(type ="ABSORB", state =NULL)) row <- row %>%slice(1) probs <-c(row$p_TD, row$p_SAF, row$p_TOV, row$p_TODS, row$p_PUNT, row$p_FG)names(probs) <-c("TD","SAFETY","TURNOVER","TODS","PUNT","FG_ATT") p_cont <-max(0, 1-sum(probs, na.rm =TRUE)) draw <-sample(c(names(probs), "CONTINUE"), size =1, prob =c(probs, p_cont), replace =TRUE)if (draw !="CONTINUE") return(list(type = draw, state =NULL))# continue: sample a next (down, togo_bin, yl_bin) from empirical pool pool <- row$next_states[[1]]if (nrow(pool) ==0) return(list(type ="ABSORB", state =NULL)) ix <-sample(seq_len(nrow(pool)), 1)list(type ="CONTINUE", state = pool[ix, ])}```Then, compute an EP curve directly from `nflfastR`'s `ep` column. For simulation we'll just roll forward with the empirical transitions until we hit a terminal outcome and assign points:- `TD` = +7 (the extra point was assumed)- `FG_ATT` is not guaranteed points; if historical next play is `FG_ATT`, we'll simulate the FG using our kicker model (instead of blindly awarding 3).- `PUNT`, `TURNOVER`, `SAFETY` → opponent ball; here we'll stop the drive and return 0 points for the offense. (This can be expanded to opponent-return EP later.)```{r A simple traditional EP baseline}# helper: map yl_bin back to center yardline_100 estimate for FG distance if neededylbin_to_yardline100 <-function(yl_bin) {# yl_bin was built from (100 - yardline_100)/5; invert approximately# We'll map to the *mean* inside the bin: yl_lo <- yl_bin*5 yl_hi <- yl_lo +5# these are yards FROM own GL to LOS; we want yardline_100 (to opp EZ) yard_from_own <- (yl_lo + yl_hi)/2 yardline_100 <-100- yard_from_own yardline_100}simulate_drive_traditional <-function(start_yl100,max_plays =100,xp_points =1, td_points =6) {# start state: 1st & 10 at start_yl100 down <- 1L togo_bin <-binner(10, togo_cuts) yl_bin <-pmin(20L, pmax(0L, floor((100- start_yl100)/5))) points <-0for (i in1:max_plays) { res <-sample_next(down, togo_bin, yl_bin)if (res$type =="ABSORB") breakif (res$type =="CONTINUE") { down <-as.integer(res$state$down_next) togo_bin <-as.integer(res$state$togo_bin_next) yl_bin <-as.integer(res$state$yl_bin_next)next }if (res$type =="TD") { points <- td_points + xp_points # keep simple; plug your PAT/2pt model if desiredbreak }if (res$type =="FG_ATT") {# Approx kick distance from current yl_bin yl100_now <-ylbin_to_yardline100(yl_bin) kd <- yl100_now +17 make <-rbinom(1, 1, p_make_fg(kd))if (make ==1) points <-3else points <-0break }# PUNT / TURNOVER / TODS / SAFETY -> end drive with 0 for offense (expand later if modeling next-possession EP) points <-ifelse(res$type =="SAFETY", -2, 0) # if you want to charge the offense; usually just 0 EP for offense viewpointbreak } points}```Next, simulate the Early FG model. As discussed above, the instant the Team crosses into FG range (`yardline_100` ≤ `fg_range_min_yd100`), attempt a FG. Note: I still use the same empirical transitions to "walk" the ball down the field prior to the `fg_thresh`, but I intercept when FG range is reached. This means that prior to the early FG zone, both models should look the same.```{r Early-FG policy simulation}simulate_drive_early_fg <-function(start_yl100,range_min_yl100 = fg_range_min_yd100,max_plays =100) { down <- 1L togo_bin <-binner(10, togo_cuts) yl_bin <-pmin(20L, pmax(0L, floor((100- start_yl100)/5)))for (i in1:max_plays) {# Check range *before* calling next play (we kick as soon as we're in range) yl100_now <-ylbin_to_yardline100(yl_bin)if (yl100_now <= range_min_yl100) { kd <- yl100_now +17 make <-rbinom(1, 1, p_make_fg(kd))return(ifelse(make ==1, 3, 0)) # simplified: offense points only } res <-sample_next(down, togo_bin, yl_bin)if (res$type =="ABSORB") return(0)if (res$type =="CONTINUE") { down <-as.integer(res$state$down_next) togo_bin <-as.integer(res$state$togo_bin_next) yl_bin <-as.integer(res$state$yl_bin_next)next }if (res$type =="TD") return(7) # simple TD+XPif (res$type =="FG_ATT") {# Traditional behavior would attempt; Early-FG would've tried earlier if in range,# but if we haven't been in range yet and a historical FG_ATT appears (rare), simulate it. kd <-ylbin_to_yardline100(yl_bin) +17 make <-rbinom(1, 1, p_make_fg(kd))return(ifelse(make ==1, 3, 0)) }# Other terminals => 0return(0) }0}```Finally, I ran head to head simulations calculating the expected points per drive ("EPD") from a set of starting yard lines for both the Traditional Model and the Early FG Model. Simulations for each model were repeated 10,000 times and the results were compared.## Model OutputsMuch of the comparisons between the Early FG model and the Traditional Model are dependent on the minimum FG make probability.The chart below shows the model and the actual historical FG make probability vs field position. For example, if you set a 50% make probability, then you need to kick from the Opponent's 40 yard line of closer.```{r chart make prob vs field position}# Grid of thresholdsfg_thresholds <-seq(0, 1, by =0.05)# Possible LOS yardline_100 values for kicksyl_grid <-1:80kick_grid <- yl_grid +17# Model probabilities for each possible kickprob_df <-data.frame(yardline_100 = yl_grid,kick_dist = kick_grid,make_prob =p_make_fg(kick_grid))# Map threshold → farthest yardline_100 and kick distancerange_map <-lapply(fg_thresholds, function(thresh) { in_range <- prob_df %>%filter(make_prob >= thresh)if (nrow(in_range) ==0) {tibble(fg_thresh = thresh, yardline_100 =NA, kick_dist =NA) } else {tibble(fg_thresh = thresh,yardline_100 =max(in_range$yardline_100, na.rm =TRUE),kick_dist =max(in_range$kick_dist, na.rm =TRUE) ) }}) %>%bind_rows()# Empirical FG data by yardline_100empirical_df <- clean_pbp %>%filter(play_type =="field_goal",!is.na(field_goal_result),!is.na(yardline_100)) %>%mutate(make =as.integer(field_goal_result =="made"),kick_dist = yardline_100 +17 ) %>%group_by(kick_dist, yardline_100) %>%summarise(make_rate =mean(make), .groups ="drop")# Bar chart for yardline_100ggplot(range_map, aes(x = fg_thresh, y = yardline_100)) +geom_col(fill ="skyblue", alpha =0.7) +geom_point(data = empirical_df, aes(x = make_rate, y = yardline_100), inherit.aes =FALSE, color ="red", size =2, alpha =0.7) +scale_x_continuous(labels = scales::percent_format(accuracy =1)) +scale_y_reverse() +theme_minimal() +theme(panel.grid.minor.x =element_blank(),plot.title =element_text(size =20, face ="bold"),plot.subtitle =element_text(size =12),plot.caption =element_text(colour ="grey60"),plot.title.position ="plot" ) +labs(title ="FG Make Probability Threshold vs Field Position",subtitle ="Bars = model range frontier, Points = empirical FG yardline_100 & make rate",caption ="jstoetz.com",x ="FG Make Probability Threshold",y ="Yards from Opponent End Zone (yardline_100)" ) ```The second chart relates the make probability to the kick distance rather than field positon. Recall that since 17 yards are added for the holder and end zone to the line of scrimmage, a 50% probability of a make equates to a kick distance of 57 yards.```{r chart make prob vs kick distance}# Bar chart for kick distanceggplot(range_map, aes(x = fg_thresh, y = kick_dist)) +geom_col(fill ="lightgreen", alpha =0.7) +geom_point(data = empirical_df, aes(x = make_rate, y = kick_dist), inherit.aes =FALSE, color ="blue", size =2, alpha =0.7) +scale_x_continuous(labels = scales::percent_format(accuracy =1)) +labs(title ="FG Make Probability Threshold vs Kick Distance",subtitle ="Bars = model range frontier, Points = empirical FG distance & make rate",caption ="jstoetz.com",x ="FG Make Probability Threshold",y ="Kick Distance (yards)" ) +theme_minimal() +theme(panel.grid.minor.x =element_blank(),plot.title =element_text(size =20, face ="bold"),plot.subtitle =element_text(size =12),plot.caption =element_text(colour ="grey60"),plot.title.position ="plot" ) ```### 85% FG Percentage ThreshholdAt `fg_thresh` = 0.85, the range starts around the opponent's 22 yard line. From those distances, the Traditional Model drives still have a solid chance to convert first downs and push for a TD. This simulation compared kicking a FG immediately once the ball was within range vs the Traditional Model.```{r 85 head to head experiments}fg_thresh <-0.85simulate_head_to_head <-function(starts =c(85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25), # yardline_100n_sims =2000) {map_dfr(starts, function(yl100) { trad <-replicate(n_sims, simulate_drive_traditional(yl100)) early <-replicate(n_sims, simulate_drive_early_fg(yl100))tibble(yardline_100 = yl100,EP_traditional =mean(trad),EP_earlyFG =mean(early),EP_diff = EP_earlyFG - EP_traditional,pr_3plus_trad =mean(trad >=3),pr_3plus_early =mean(early >=3) ) })}results_85 <-simulate_head_to_head()knitr::kable(results_85, align ="c",caption ="85% FG Percentage Threshhold") %>% kableExtra::kable_classic(full_width =FALSE)```The table output columns are defined as follows:- **yardline_100** Starting field position (yards from opponent's end zone). Lower numbers = better starting position.- **EP_traditional** Expected points per drive using historical 4th-down behavior (drive until stopped or choose FG).- **EP_earlyFG** Expected points if you kick as soon as you're within range for an **\~85% make rate**.- **EP_diff** `EP_earlyFG` − `EP_traditional`. Negative means the early FG strategy is worse.- **pr_3plus_trad** Probability the drive ends with ≥ 3 points under Traditional Model.- **pr_3plus_early** Same probability under Early FG Model strategy.```{r chart 85 make vs field position}results_85 %>%pivot_longer(cols =c(EP_traditional, EP_earlyFG),names_to ="policy", values_to ="EP") %>%ggplot(aes(x =100- yardline_100, y = EP, linetype = policy)) +geom_line(size =1) +labs(x ="Yards from Opponent End Zone (smaller = closer)",y ="Expected Points per Drive",title ="Traditional vs Early-FG: Expected Points by Field Position",subtitle =paste0("Model make prob ≥ ", fg_thresh*100, "%" ),caption ="jstoetz.com") +theme_minimal() +theme(panel.grid.minor.x =element_blank(),plot.title =element_text(size =20, face ="bold"),plot.subtitle =element_text(size =12),plot.caption =element_text(colour ="grey60"),plot.title.position ="plot" )``````{r chart 85 FG make prob vs model}plot_fg_make_prob <-function(clean_pbp, fg_mod, fg_thresh =0.85) {library(dplyr)library(ggplot2)# Empirical make rates by yardline_100 fg_emp <- clean_pbp %>%filter(play_type =="field_goal",!is.na(field_goal_result),!is.na(yardline_100)) %>%mutate(make =as.integer(field_goal_result =="made")) %>%group_by(yardline_100) %>%summarise(attempts =n(),makes =sum(make),make_rate = makes / attempts,.groups ="drop" )# Model predictions for every yardline_100 pred_df <-data.frame(yardline_100 =seq(min(fg_emp$yardline_100),max(fg_emp$yardline_100),by =1)) pred_df$kick_dist <- pred_df$yardline_100 +17 pred_df$pred_prob <-predict(fg_mod, newdata = pred_df, type ="response")# Determine shaded "in range" zone based on model in_range_df <- pred_df %>%filter(pred_prob >= fg_thresh)if (nrow(in_range_df) >0) { range_min <-min(in_range_df$yardline_100) # closest range_max <-max(in_range_df$yardline_100) # farthest still in range } else { range_min <-NA range_max <-NA }ggplot() +# Shaded zone for in-range yardlines { if (!is.na(range_min) &&!is.na(range_max))geom_rect(aes(xmin = range_min, xmax = range_max,ymin =0, ymax =1),fill ="green", alpha =0.15) } +# Empirical pointsgeom_point(data = fg_emp, aes(x = yardline_100, y = make_rate),color ="blue", alpha =0.6, size =2) +# Model linegeom_line(data = pred_df, aes(x = yardline_100, y = pred_prob),color ="red", size =1) +scale_x_reverse() +# so closer to goal is on the rightlabs(x ="Yards from Opponent End Zone (yardline_100)",y ="Probability of Made FG",title ="FG Make Probability vs. Field Position",subtitle =paste0("Shaded = model make prob ≥ ", fg_thresh*100, "%" ),caption ="jstoetz.com" ) +theme_minimal() +theme(panel.grid.minor.x =element_blank(),plot.title =element_text(size =20, face ="bold"),plot.subtitle =element_text(size =12),plot.caption =element_text(colour ="grey60"),plot.title.position ="plot" )}plot_fg_make_prob(clean_pbp, fg_mod, fg_thresh =0.85)```### 75% FG Percentage Threshhold```{r 75 head to head experiments}fg_thresh <-0.75simulate_head_to_head <-function(starts =c(85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25), # yardline_100n_sims =2000) {map_dfr(starts, function(yl100) { trad <-replicate(n_sims, simulate_drive_traditional(yl100)) early <-replicate(n_sims, simulate_drive_early_fg(yl100))tibble(yardline_100 = yl100,EP_traditional =mean(trad),EP_earlyFG =mean(early),EP_diff = EP_earlyFG - EP_traditional,pr_3plus_trad =mean(trad >=3),pr_3plus_early =mean(early >=3) ) })}results_75 <-simulate_head_to_head()knitr::kable(results_75, align ="c",caption ="75% FG Percentage Threshhold") %>% kableExtra::kable_classic(full_width =FALSE)``````{r chart 75 make vs field position}results_75 %>%pivot_longer(cols =c(EP_traditional, EP_earlyFG),names_to ="policy", values_to ="EP") %>%ggplot(aes(x =100- yardline_100, y = EP, linetype = policy)) +geom_line(size =1) +labs(x ="Yards from Opponent End Zone (smaller = closer)",y ="Expected Points per Drive",title ="Traditional vs Early-FG: Expected Points by Field Position",subtitle =paste0("Model make prob ≥ ", fg_thresh*100, "%" ),caption ="jstoetz.com") +theme_minimal() +theme(panel.grid.minor.x =element_blank(),plot.title =element_text(size =20, face ="bold"),plot.subtitle =element_text(size =12),plot.caption =element_text(colour ="grey60"),plot.title.position ="plot" )``````{r chart 75 FG make prob vs model}plot_fg_make_prob(clean_pbp, fg_mod, fg_thresh =0.75)```### 65% FG Percentage Threshhold```{r 65 head to head experiments}fg_thresh <-0.65simulate_head_to_head <-function(starts =c(85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25), # yardline_100n_sims =2000) {map_dfr(starts, function(yl100) { trad <-replicate(n_sims, simulate_drive_traditional(yl100)) early <-replicate(n_sims, simulate_drive_early_fg(yl100))tibble(yardline_100 = yl100,EP_traditional =mean(trad),EP_earlyFG =mean(early),EP_diff = EP_earlyFG - EP_traditional,pr_3plus_trad =mean(trad >=3),pr_3plus_early =mean(early >=3) ) })}results_65 <-simulate_head_to_head()knitr::kable(results_65, align ="c",caption ="65% FG Percentage Threshhold") %>% kableExtra::kable_classic(full_width =FALSE)``````{r chart 65 make vs field position}results_65 %>%pivot_longer(cols =c(EP_traditional, EP_earlyFG),names_to ="policy", values_to ="EP") %>%ggplot(aes(x =100- yardline_100, y = EP, linetype = policy)) +geom_line(size =1) +labs(x ="Yards from Opponent End Zone (smaller = closer)",y ="Expected Points per Drive",title ="Traditional vs Early-FG: Expected Points by Field Position",subtitle =paste0("Model make prob ≥ ", fg_thresh*100, "%" ),caption ="jstoetz.com") +theme_minimal() +theme(panel.grid.minor.x =element_blank(),plot.title =element_text(size =20, face ="bold"),plot.subtitle =element_text(size =12),plot.caption =element_text(colour ="grey60"),plot.title.position ="plot" )``````{r chart 65 FG make prob vs model}plot_fg_make_prob(clean_pbp, fg_mod, fg_thresh =0.65)```### 50% FG Percentage Threshhold```{r 50 head to head experiments}fg_thresh <-0.50simulate_head_to_head <-function(starts =c(85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25), # yardline_100n_sims =2000) {map_dfr(starts, function(yl100) { trad <-replicate(n_sims, simulate_drive_traditional(yl100)) early <-replicate(n_sims, simulate_drive_early_fg(yl100))tibble(yardline_100 = yl100,EP_traditional =mean(trad),EP_earlyFG =mean(early),EP_diff = EP_earlyFG - EP_traditional,pr_3plus_trad =mean(trad >=3),pr_3plus_early =mean(early >=3) ) })}results_50 <-simulate_head_to_head()knitr::kable(results_50, align ="c",caption ="50% FG Percentage Threshhold") %>% kableExtra::kable_classic(full_width =FALSE)``````{r chart 50 make vs field position}results_50 %>%pivot_longer(cols =c(EP_traditional, EP_earlyFG),names_to ="policy", values_to ="EP") %>%ggplot(aes(x =100- yardline_100, y = EP, linetype = policy)) +geom_line(size =1) +labs(x ="Yards from Opponent End Zone (smaller = closer)",y ="Expected Points per Drive",title ="Traditional vs Early-FG: Expected Points by Field Position",subtitle =paste0("Model make prob ≥ ", fg_thresh*100, "%" ),caption ="jstoetz.com") +theme_minimal() +theme(panel.grid.minor.x =element_blank(),plot.title =element_text(size =20, face ="bold"),plot.subtitle =element_text(size =12),plot.caption =element_text(colour ="grey60"),plot.title.position ="plot" )``````{r chart 50 FG make prob vs model}plot_fg_make_prob(clean_pbp, fg_mod, fg_thresh =0.5)```## Minimum FG Make ProbabilityHaving struck out defining specific `fg_thresh` values, I decided to expand my view to see if the Early FG model ever had higher EP than a Traditional Model. The steps were as below:- Pick a grid of `fg_thresh` values (0.50 → 0.95).- Pick a set of starting `yardline_100` values which is the Team starting field position (30 → 75)- Run both the Traditional Model and Early FG strategies for each combination.- Find the cross-over point (where `EP_diff` is positive).- Plot the curves an tables so you can visually see where the Early FG starts to make sense.```{r min FG charts}# Function to run one sweep value of fg_threshsimulate_sweep <-function(fg_thresh, starts, n_sims =1000) {# Set the global fg_range_min_yd100 for this threshold yd100_grid <-1:80 mk <-p_make_fg(yd100_grid +17) fg_range_min_yd100 <-max(yd100_grid[mk >= fg_thresh], na.rm =TRUE)map_dfr(starts, function(yl100) { trad <-replicate(n_sims, simulate_drive_traditional(yl100)) early <-replicate(n_sims, simulate_drive_early_fg(yl100,range_min_yl100 = fg_range_min_yd100))tibble(fg_thresh = fg_thresh,yardline_100 = yl100,EP_traditional =mean(trad),EP_earlyFG =mean(early),EP_diff =mean(early) -mean(trad) ) })}# Sweep parametersfg_thresholds <-seq(0.50, 0.95, by =0.05)start_positions <-seq(30, 75, by =5) # from opp 30 to own 25# Run the sweepsweep_results <-map_dfr(fg_thresholds, simulate_sweep, starts = start_positions)# Identify crossover points (where EP_diff >= 0)crossover_points <- sweep_results %>%group_by(yardline_100) %>%summarise(min_thresh_break_even =min(fg_thresh[EP_diff >=0], na.rm =TRUE),.groups ="drop" )# Plot EP_diff curves for each start positionggplot(sweep_results, aes(x = fg_thresh, y = EP_diff, color =factor(yardline_100))) +geom_hline(yintercept =0, linetype ="dashed") +geom_line(size =1) +labs(title ="EP Difference (Early FG - Traditional) Across FG Thresholds",x ="FG Make Probability Threshold",y ="EP Difference",caption ="jstoetz.com",color ="Start Yardline_100" ) +theme_minimal() +theme(panel.grid.minor.x =element_blank(),plot.title =element_text(size =20, face ="bold"),plot.subtitle =element_text(size =12),plot.caption =element_text(colour ="grey60"),plot.title.position ="plot" )ggplot(sweep_results, aes(x = fg_thresh, y = yardline_100, fill = EP_diff)) +geom_tile() +geom_text(size =3,fontface ="bold",aes(label = EP_diff, colour ="white" ) ) +scale_colour_identity() +# Use the specified colors without a legendscale_fill_viridis_c(option ="H", guide =FALSE) +scale_x_continuous(breaks =seq(0.50, 0.95, by =0.05)) +scale_y_continuous(breaks =seq(30, 75, by =5)) +labs(x ="\nMinimum FG Percentage Threshhold",y ="Yards from Opponent End Zone (yardline_100)",title ="EP Difference - Field Position vs FG Pct Threshhold",subtitle ="Expected Points per drive difference (Early FG minus Trad Model) based on minimum field goal percentage and yards from opponents endzone",caption ="jstoetz.com" ) +#theme_bw() + theme(plot.title =element_text(size =20, face ="bold", colour ="black"),plot.subtitle =element_text(size =12, colour ="black"),plot.caption =element_text(colour ="grey60"),plot.title.position ="plot",axis.title.x =element_text(size =10, colour ="black"),axis.text.x =element_text(size =10, colour ="black"),axis.text.y =element_text(size =10, colour ="black"),panel.border =element_blank(),panel.grid.major =element_blank(),panel.grid.minor =element_blank(),panel.background =element_blank() )```## Break-Even Calculation for Early FG ModelTo this point, I failed to find a situation where the Early FG model beat the Traditional Model when comparing points per drive. My initial explanation was that NFL kickers aren't making a high engough percentage of field goals from far enough away. Which brings me to the last question: **what make percentage would be required for the Early FG model to break even versus the TM model from a given start, if we commit to kicking at a particular field position?** How good would a kicker have to be to make the MoneyballFB Model *better* than the existing NFL approach?Turns out there is no real limit.The plot below is showing that for every tested starting field position and kick distance, the **minimum make percentage required for “Early FG” ≥ “Traditional EP” is 100%.**That means:- Even if a kicker could make every single attempt from those distances, the Early FG strategy just barely ties the traditional strategy at best.- At any real-world FG% (<100%), Early FG is always worse in terms of expected points.- The reason is that a traditional drive has upside beyond 3 points (touchdowns, closer FGs, penalties in your favor), while Early FG caps you at 3 points and adds the risk of a miss.Thus, **there is no distance (under current NFL kicking skill levels and rules) where “kick immediately” outperforms the traditional strategy.**```{r frontier kickers}# ---- Probability of ever reaching a frontier (yl100) from a start ----reach_prob <-function(start_yl100, frontier_yl100, n_sims =1000, max_plays =200) { reached <-logical(n_sims)for (i inseq_len(n_sims)) { down <- 1L togo_bin <-binner(10, togo_cuts) yl_bin <-pmin(20L, pmax(0L, floor((100- start_yl100)/5))) hit <-FALSEfor (j inseq_len(max_plays)) { yl100_now <-ylbin_to_yardline100(yl_bin)if (yl100_now <= frontier_yl100) { hit <-TRUE; break } res <-sample_next(down, togo_bin, yl_bin)if (res$type !="CONTINUE") break down <-as.integer(res$state$down_next) togo_bin <-as.integer(res$state$togo_bin_next) yl_bin <-as.integer(res$state$yl_bin_next) } reached[i] <- hit }mean(reached)}# ---- Break-even p* for (start, kick_dist) pairs; also compares to your model p_make_fg ----break_even_grid <-function(starts, kick_dists,n_sims_ep =30, n_sims_reach =30) { starts <-as.integer(starts) kick_dists <-as.integer(kick_dists)# Cache Traditional EP by start to avoid re-simulating needlessly ep_trad_map <-tibble(start_yl100 =unique(starts)) %>%mutate(EP_traditional =map_dbl(start_yl100, ~mean(replicate(n_sims_ep, simulate_drive_traditional(.x))) ))expand_grid(start_yl100 =unique(starts),kick_dist =unique(kick_dists)) %>%mutate(frontier_yl100 =pmax(1L, round(kick_dist - 17L))) %>%left_join(ep_trad_map, by ="start_yl100") %>%mutate(pi_reach =map2_dbl(start_yl100, frontier_yl100,~reach_prob(.x, .y, n_sims = n_sims_reach)),p_required =if_else(pi_reach >0, EP_traditional / (3* pi_reach),NA_real_),p_required =pmin(pmax(p_required, 0), 1),p_model =p_make_fg(kick_dist),early_fg_EP_positive = p_model >= p_required,EP_early_model =3* p_model * pi_reach,EP_diff_model = EP_early_model - EP_traditional ) %>%arrange(start_yl100, kick_dist)}# ---- Example: specify your starts & kick distances ----starts_test <-c(95, 85, 75, 65, 55, 45) # yardline_100 startsdists_test <-c(45, 50, 55, 60, 65, 70, 75, 85, 95, 100) # kick distances in yardsbe_tbl <-break_even_grid(starts_test, dists_test,n_sims_ep =30, n_sims_reach =30)# Nicely formatted viewbe_tbl %>%transmute( start_yl100, kick_dist, frontier_yl100,EP_traditional =round(EP_traditional, 3),pi_reach =round(pi_reach, 3),p_required_pct = scales::percent(p_required, accuracy =0.1),p_model_pct = scales::percent(p_model, accuracy =0.1),EP_diff_model =round(EP_diff_model, 3),EP_positive =if_else(early_fg_EP_positive, "YES", "NO") ) %>%arrange(desc(EP_diff_model)) %>% knitr::kable(align ="c",caption ="Break-even make% for Early-FG to exceed Traditional EP (by start & distance)") %>% kableExtra::kable_classic(full_width =FALSE)# Heatmap of required make% (p_required)ggplot(be_tbl, aes(x = kick_dist, y = start_yl100, fill = p_required)) +geom_tile(color ="white") +scale_fill_viridis_c(option ="plasma",limits =c(0,1),labels = scales::percent,name ="Min FG% Required" ) +geom_text(aes(label = scales::percent(round(p_required, 2))),color ="black", size =3 ) +scale_x_continuous("Kick Distance (yards)", breaks =seq(40, 100, 5)) +scale_y_reverse("Starting Yardline_100 (yards to goal)", breaks =seq(40,95,5)) +theme_minimal(base_size =13) +ggtitle("Break-even FG Make% by Start Position & Kick Distance",subtitle ="Each cell = minimum FG% needed for Early-FG ≥ Traditional EP")```## Notes & Research- american football - Could an immediate field goal be advantageous in NFL? - Sports Stack Exchange - [Link](https://sports.stackexchange.com/questions/25216/could-an-immediate-field-goal-be-advantageous-in-nfl) - [DB](https://www.dropbox.com/scl/fi/wad9a2vkpz0wjpluyyy70/2025-Aug-13-american-football-Could-an-immediate-field-goal-be-advantageous-in-NFL-Sports-Stack-Exchange.pdf?rlkey=nsvol2z5rkbwo4xlchb27z1s7&dl=0)- Field Goal Success Probabilities by Direction \| NFL Football Operations - [Link](https://operations.nfl.com/gameday/analytics/stats-articles/field-goal-success-probabilities-by-direction/#) - [DB](https://www.dropbox.com/scl/fi/f6652swvr7e5nwl0pnytr/2025-Aug-13-Field-Goal-Success-Probabilities-by-Direction-NFL-Football-Operations.pdf?rlkey=lhreuyxam640w1chywv2izcu4&dl=0)- Visualizing NFL Kicker Accuracy Trends (1999-2024) // Conor McLaughlin - [Link](https://conormclaughlin.net/2025/01/visualizing-nfl-kicker-accuracy-trends-1999-2024/) - [DB](https://www.dropbox.com/scl/fi/iygvgvs87depttl50jx6e/2025-Aug-13-Visualizing-NFL-Kicker-Accuracy-Trends-1999-2024-Conor-McLaughlin.pdf?rlkey=g8350l7zh2x9ivbbfb7zoxjgf&dl=0)- Using Ridgeline Plots to Visualize the NFL's Shift Towards Longer Field Goal Attempts // Conor McLaughlin - [Link](https://conormclaughlin.net/2025/01/using-ridgeline-plots-to-visualize-the-nfls-shift-towards-longer-field-goal-attempts/) - [DB](https://www.dropbox.com/scl/fi/p4n3ktlvza2e1d3w8dxhy/2025-Aug-13-Using-Ridgeline-Plots-to-Visualize-the-NFL-s-Shift-Towards-Longer-Field-Goal-Attempts-Conor-McLaughlin.pdf?rlkey=tv2h2672eyugqdjfrlfc9vmjk&dl=0)- Field goal - Wikipedia - [Link](https://en.m.wikipedia.org/wiki/Field_goal) - [DB](https://www.dropbox.com/scl/fi/p6xqryi0hfsgadanqppnn/2025-Aug-13-Field-goal-Wikipedia.pdf?rlkey=ylikcm9ccbx71fbimjg3dryne&dl=0)- Mathematically If a Team Invests in a kicker, could they kick it every drive? : r/NFLNoobs - [Link](https://www.reddit.com/r/NFLNoobs/comments/1hyo8n9/mathematically_if_a_team_invests_in_a_kicker/) - [DB](https://www.dropbox.com/scl/fi/vbee6vyj0evgoq4ml0hfv/2025-Aug-13-Mathematically-If-a-Team-Invests-in-a-kicker-could-they-kick-it-every-drive-r-NFLNoobs-1.pdf?rlkey=ztbkep1oo7wuagndcl0co35fo&dl=0)- What are Expected Points Added (EPA) in the NFL \| nfelo - [Link](https://www.nfeloapp.com/analysis/expected-points-added-epa-nfl/) - [DB](https://www.dropbox.com/scl/fi/ice1nxnx87s36648bdjp7/2025-Aug-13-What-are-Expected-Points-Added-EPA-in-the-NFL-nfelo.pdf?rlkey=5km88cl1aiaxualfk1io0recv&dl=0)- Operations research on Football - [Link](https://pubsonline.informs.org/doi/pdf/10.1287/opre.19.2.541) - [DB](https://www.dropbox.com/scl/fi/qznt34g0o73rjxmd1wgdw/2025-Aug-13.pdf?rlkey=6lco6qkfx8nwlcdvllaae5oo1&dl=0)- College Football Expected Points Model Fundamentals - Part I • cfbfastR - [Link](https://cfbfastr.sportsdataverse.org/articles/college-football-expected-points-model-fundamentals-part-i.html) - [DB](https://www.dropbox.com/scl/fi/08ee3lnx4wdj2jr1uvt1c/2025-Aug-13-College-Football-Expected-Points-Model-Fundamentals-Part-I-cfbfastR.pdf?rlkey=9ve0r3jnkjsee5okopllcrmfu&dl=0)- College Football Expected Points Model Fundamentals - Part II • cfbfastR - [Link](https://cfbfastr.sportsdataverse.org/articles/college-football-expected-points-model-fundamentals-part-ii.html) - [DB](https://www.dropbox.com/scl/fi/o7ukdsfjv9yayi7yrc0zb/2025-Aug-13-College-Football-Expected-Points-Model-Fundamentals-Part-II-cfbfastR.pdf?rlkey=bdvnps835sj2c2leptnp1rr4x&dl=0)- College Football Expected Points Model Fundamentals - Part III • cfbfastR - [Link](https://cfbfastr.sportsdataverse.org/articles/college-football-expected-points-model-fundamentals-part-iii.html) - [DB](https://www.dropbox.com/scl/fi/yzqhyyg0u5isv8jm5j35c/2025-Aug-13-College-Football-Expected-Points-Model-Fundamentals-Part-III-cfbfastR.pdf?rlkey=ce2okse6efq8e6oibzgqbjv4b&dl=0)- What is the Predicted Probability of a Field Goal Given Yards - [Link](https://ckchiruka.github.io/projects/fieldgoals.pdf) - [DB](https://www.dropbox.com/scl/fi/4loxbd4zcov5zg9cg6lk8/2025-Aug-13-1.pdf?rlkey=2aeio31jdje1hvxsfh14wollp&dl=0)- Advanced Football Analytics (formerly Advanced NFL Stats): Expected Point Values - [Link](https://www.advancedfootballanalytics.com/2009/12/expected-point-values.html) - [DB](https://www.dropbox.com/scl/fi/fqjr8x4g81o7f6figh35x/2025-Aug-14-Advanced-Football-Analytics-formerly-Advanced-NFL-Stats-Expected-Point-Values.pdf?rlkey=p9uh6rozrrarz16lwcs1pyn1y&dl=0)- An R package to quickly obtain clean and tidy NFL play by play data • nflfastR - [Link](https://www.nflfastr.com/) - [DB](https://www.dropbox.com/scl/fi/m93u3le88txml0wpp9x8g/2025-Aug-14-An-R-package-to-quickly-obtain-clean-and-tidy-NFL-play-by-play-data-nflfastR.pdf?rlkey=qtpgaq80vjpyvjai0xf3d5plr&dl=0)