#Packages Used
library(readr)
library(ggplot2)
library(tidyr)
library(dplyr)
library(stringr)
library(fmsb)
library(ggrepel)
library(DT)
library(purrr)
#Reading in 2025 and 2026 Data
Masters2025 <- read_csv("/Users/kjerstiwarwick/Desktop/Course Work/INFO 3320/Final Project/2025MastersGolf.csv")
Masters2026 <- read_csv('/Users/kjerstiwarwick/Desktop/Course Work/INFO 3320/Final Project/2026MastersGolf.csv')
#McIlroy Filtered Data
mcilroy_2025 <- Masters2025 |> filter(str_detect(player_name, "McIlroy"))
mcilroy_2026 <- Masters2026 |> filter(str_detect(player_name, "McIlroy"))First Steps Down Magnolia Lane
This project examines Rory McIlroy’s performance across his back-to-back Masters championships in 2025 and 2026, both earning him golf’s most coveted prize: the Green Jacket.
McIlroy, a Northern Irish professional competing on the PGA Tour and DP World Tour, is one of the game’s most decorated players. A former world No. 1 who held the ranking for 100 weeks across his career, he entered Augusta in 2025 as one of the sport’s greatest players still missing a Masters title. That changed when he defeated Justin Rose in a sudden-death playoff, securing his sixth major and completing the career Grand Slam — making him only the sixth player and first European to achieve the feat. He returned in 2026 and won by one stroke at 12-under 276, becoming just the fourth player in history — and the first since Tiger Woods in 2001–2002 — to win back-to-back titles at Augusta National.
My interest in this topic is personal. I played golf on my high school team for three years and later worked at Erin Hills Golf Course, host of the 2017 U.S. Men’s Open and the 2025 U.S. Women’s Open. Immersed in the history of that tournament and the players who competed there, I developed a deeper appreciation for the analytical side of professional golf — which directly inspired this project.
Strokes gained is the same framework PGA analysts, caddies, and coaches use to evaluate player performance. Understanding it puts individuals in conversation with how the sport can actually be analyzed at a professional level. McIlroy’s back-to-back wins created a natural controlled comparison – same course, same player, and consecutive years – which is rare in sports data. This structure lets us ask whether player improvement was real for those two years and find where the improvement came from, rather than just describing the outcomes.
The Approach
The approach shot is the critical moment where you commit to your strategy and aim directly at the target.
Does McIlroy’s back-to-back Masters success reflect absolute improvement or relative dominance?
Teeing Off
Packages & .CSV
Key Colors
| Color | Code |
|---|---|
| “Masters Green” | #006747 |
| Gold | #EFBF04 |
| Scarlet Red | #E31B23 |
| Light Green | #87B87F |
| Gray | N/A |
The Green Book - Data & Variables
Augusta’s Green Book is legendary in golf as the secret, highly detailed course notes that only a handful of caddies possess.
The Scorecards - The Data
datatable(Masters2025, filter = "top")datatable(Masters2026, filter = "top")Variables Explained
Strokes Gained is a performance metric that measures how many shots are gained or lost against a predetermined bench mark.
Strokes gained gives a breakdown of every part of the round. Instead of counting total putts or fairway hits, golf uses strokes gained to tell you where you are losing or saving shots.
SG: Total Benchmark
At the Masters, players will typically need to benchmark at least +1.50-2.00 SG: Total per round against the field. If the field scoring average for the day is 72 and you shoot a 69, your SG: Total is +3. If you shoot a 75, your total is -3.
Augusta National
The two non shots gained variables are specific to Par 4s and 5s. There are ten par 4s and four par 5s at August National Golf Club, meaning there are 56 holes analyzed for drive distance and accuracy in the data set.
| Official Term | Variable | Defined |
|---|---|---|
| SG:Putt | sg_putt | Measures how many strokes a player gains (or loses) on the putting green. |
| SG:Around the Green (ARG) | sg_arg | Measures a player’s performance on all shots played within 30-50 yards of the hole. Includes tee shots, approach shots from further out, and puts on the green. |
| SG:Approach (APP) | sg_app | Measures shot hit into the green, including Par 3s and any shot meant to reach the green. |
| SG:Off the Tee (OTT) | sg_ott | Measures a player’s performance on all tee shots (excluding Par 3s), evaluating how many strokes a player gains or loses against the field average by combining both the distance and accuracy of drives. |
| SG:Tee to Goal (TTG) | sg_t2g | Combines OTT, App, ATG to measure all non-putting play. |
| SG:Total | sg_total | Measures how many strokes a player gained or lost against the field average over an entire round of tournament. A positive number means the player played better than the field average, while a negative numbers means they performed worse. |
| SG:Ball Striking (BS) | sg_bs | Evaluates a player’s combined performance for hitting the ball off the tee and during their approach shots into the green. Combines SG:OTT and SG:APP. |
| Distance | distance | Average distance of all drives on Par 4s and 5s. Reloads are included. |
| Accuracy | accuracy | Percentage of fairways hit on Par 4s and 5s. Reloads not included. |
Whiffs - Null Values
Please note for 41 players that did not make the cut to round 4, null values will fill in for scores, measurements, and position. I felt it was important to still include these 41 players for larger calculations that encompass the entire tournament and event totals.
The 19th Hole - Summary Stats
The 19th Hole commonly refers to going to the bar after that round of golf. It is when the game and scores are reflected on.
#Summary Stats
summary_vars <- c("total_score", "sg_putt", "sg_arg", "sg_app", "sg_ott",
"sg_t2g", "sg_total", "sg_bs", "distance", "accuracy")summary(Masters2025[, summary_vars]) total_score sg_putt sg_arg sg_app
Min. :-11.000 Min. :-3.1100 Min. :-2.750 Min. :-3.1600
1st Qu.: -2.000 1st Qu.:-0.8900 1st Qu.:-0.470 1st Qu.:-0.8100
Median : 4.000 Median :-0.0600 Median :-0.030 Median :-0.0600
Mean : 2.516 Mean :-0.1507 Mean :-0.106 Mean :-0.1685
3rd Qu.: 6.000 3rd Qu.: 0.6800 3rd Qu.: 0.470 3rd Qu.: 0.6700
Max. : 17.000 Max. : 2.3300 Max. : 1.630 Max. : 2.3100
sg_ott sg_t2g sg_total sg_bs
Min. :-4.46000 Min. :-7.6100 Min. :-7.3700 Min. :-5.9300
1st Qu.:-0.48000 1st Qu.:-1.2800 1st Qu.:-1.5700 1st Qu.:-0.9200
Median : 0.10000 Median :-0.1600 Median :-0.3700 Median : 0.0200
Mean :-0.04242 Mean :-0.3178 Mean :-0.4684 Mean :-0.2108
3rd Qu.: 0.54500 3rd Qu.: 0.8750 3rd Qu.: 1.1800 3rd Qu.: 0.6900
Max. : 1.50000 Max. : 3.7400 Max. : 3.4300 Max. : 2.9700
distance accuracy
Min. :255.3 Min. :0.4300
1st Qu.:293.7 1st Qu.:0.6900
Median :299.8 Median :0.7300
Mean :298.6 Mean :0.7342
3rd Qu.:305.4 3rd Qu.:0.7900
Max. :317.9 Max. :0.9300
summary(Masters2026[, summary_vars]) total_score sg_putt sg_arg sg_app
Min. :-12.000 Min. :-2.9500 Min. :-2.3800 Min. :-4.3100
1st Qu.: -2.500 1st Qu.:-0.8650 1st Qu.:-0.5150 1st Qu.:-0.7550
Median : 3.000 Median :-0.1100 Median : 0.0100 Median :-0.0400
Mean : 2.736 Mean :-0.1649 Mean :-0.1012 Mean :-0.1977
3rd Qu.: 8.000 3rd Qu.: 0.5300 3rd Qu.: 0.4400 3rd Qu.: 0.6300
Max. : 18.000 Max. : 2.8800 Max. : 2.7700 Max. : 2.4000
sg_ott sg_t2g sg_total sg_bs
Min. :-4.240 Min. :-5.8800 Min. :-7.250 Min. :-6.4400
1st Qu.:-0.670 1st Qu.:-1.8500 1st Qu.:-2.250 1st Qu.:-1.2400
Median : 0.050 Median :-0.1400 Median :-0.200 Median :-0.1100
Mean :-0.129 Mean :-0.4271 Mean :-0.594 Mean :-0.3263
3rd Qu.: 0.535 3rd Qu.: 1.1350 3rd Qu.: 1.175 3rd Qu.: 0.8500
Max. : 1.830 Max. : 3.1600 Max. : 3.550 Max. : 3.0000
distance accuracy
Min. :261.8 Min. :0.3200
1st Qu.:295.4 1st Qu.:0.6500
Median :303.3 Median :0.7100
Mean :302.5 Mean :0.7084
3rd Qu.:309.8 3rd Qu.:0.7900
Max. :330.5 Max. :0.9100
In both years, the summary stats reflect the difficulty of Augusta National. The media SG:Total sits below zero which confirms most players in the field will lose strokes to the course average. The 2026 field played slightly harder overall, the media total score rose from +4 to +3 relative to par and the mean SG:Total dropped from -0.47 to -0.59. This suggests the course played more difficult or the field was less competitive.
Driving distance increased in 2026 with a median of 303.3 yards while fairway accuracy dipped about 3%. This reflects a field that swung harder yet less precisely. We will explore this further in a moment. Putting was also a weakness in both fields. The mean for SG:Putting was -0.15 and -0.16 indicating Augusta’s famously challenging greens cost the average player strokes both years. Those who putt well were able to stand out.
Score Distributions
ggplot() +
geom_histogram(data = Masters2025,
aes(x = total_score,
fill = "2025"),
binwidth = 1,
alpha = 0.6,
color = "white") +
geom_histogram(data = Masters2026,
aes(x = total_score,
fill = "2026"),
binwidth = 1,
alpha = 0.6,
color = "white") +
scale_fill_manual(values = c("2025" = "#006747", "2026" = "#FFD700"),
name = "Year") +
labs(title = "Score Distribution: 2025 vs. 2026 Masters",
x = "Total Score (relative to par)",
y = "Number of Players") +
theme_minimal()The 2025 score distribution is tighter and bell shaped, clustering heavily at +3 to +5, pointing to a relatively consistent field. The 2026 distribution is flatter and wider, showing more players scored well under par but at the same time more players played over-par. 2026 was a more polarized tournament. The best players were more separated from the rest while the bottom of the leaderboard struggled more than in 2025.
Stokes Gained by Category
sg_levels <- c("sg_ott", "sg_app", "sg_arg", "sg_putt", "sg_t2g", "sg_bs", "sg_total")
sg_labels <- c("Off Tee", "Approach", "Around Green", "Putting", "Tee-to-Green", "Ball Striking", "Total")
sg_means_2025 <- Masters2025 |>
summarise(across(all_of(sg_levels), mean, na.rm = TRUE)) |>
pivot_longer(cols = everything(),
names_to = "category",
values_to = "mean_sg") |>
mutate(category = factor(category,
levels = sg_levels,
labels = sg_labels))
sg_means_2026 <- Masters2026 |>
summarise(across(all_of(sg_levels), mean, na.rm = TRUE)) |>
pivot_longer(cols = everything(),
names_to = "category",
values_to = "mean_sg") |>
mutate(category = factor(category,
levels = sg_levels,
labels = sg_labels))
ggplot() +
geom_col(data = sg_means_2025,
aes(x = as.numeric(category) - 0.2,
y = mean_sg,
fill = "2025"),
width = 0.35) +
geom_col(data = sg_means_2026,
aes(x = as.numeric(category) + 0.2,
y = mean_sg,
fill = "2026"),
width = 0.35) +
geom_hline(yintercept = 0,
linetype = "dashed",
color = "gray") +
scale_x_continuous(breaks = seq_along(sg_labels),
labels = sg_labels) +
scale_fill_manual(values = c("2025" = "#006747", "2026" = "#87B87F"),
name = "Year") +
labs(title = "Average Strokes Gained by Category: 2025 vs. 2026 Masters",
x = NULL,
y = "Mean Strokes Gained") +
theme_minimal()The 2025 field under performed 2026 in all categories but SG:Around the Green. The difference was most notable in SG:Approach, SG:Tee to Green, and SG:Total. The average played in 2025 lost more strokes to the benchmark than in 2026 in most skill areas. This reinforces the idea that 2026 saw greater variance.
Rory & The Field
Driving Distance vs. Accuracy
ggplot() +
geom_point(data = Masters2025, aes(x = distance,
y = accuracy,
color = "2025"),
alpha = 0.6, size = 2) +
geom_point(data = Masters2026, aes(x = distance,
y = accuracy,
color = "2026"),
alpha = 0.6, size = 2) +
geom_point(data = mcilroy_2025, aes(x = distance,
y = accuracy,
color = "2025"),
size = 5, shape = 18) +
geom_point(data = mcilroy_2026, aes(x = distance,
y = accuracy,
color = "2026"),
size = 5, shape = 18) +
geom_text(data = mcilroy_2025, aes(x = distance,
y = accuracy,
label = "McIlroy '25"),
vjust = -1,
size = 3,
color = "#006747") +
geom_text(data = mcilroy_2026, aes(x = distance,
y = accuracy,
label = "McIlroy '26"),
vjust = -1,
size = 3,
color = "#EFBF04") +
scale_color_manual(values = c("2025" = "#006747", "2026" = "#EFBF04"),
name = "Year") +
labs(title = "Driving Distance vs. Accuracy: 2025 vs. 2026 Masters",
x = "Average Drive Distance (yards)",
y = "Driving Accuracy (fairways hit %)"
) +
theme_minimal()As driving distance increases, the fairway accuracy tends to decrease which forms a mild downward trend for both years. McIlroy’s positioning tells us that in 2025 he was at about 315 yards with ~63% accuracy and already among the longest hitters in the field. In 2026 he pushed his distance to 330 yards but his accuracy dipped to ~54%. This makes him once of the longest but least accurate drivers in the tournament. The drop in accuracy shows how he can effectively recover in his approach and short game.
Radar Charts: Strokes Gained
radar_vars <- c("sg_ott", "sg_app", "sg_arg", "sg_putt")
radar_labels <- c("Off Tee", "Approach", "Around Green", "Putting")
mcilroy_radar_2025 <- as.numeric(mcilroy_2025[, radar_vars])
mcilroy_radar_2026 <- as.numeric(mcilroy_2026[, radar_vars])
field_mean_2025 <- colMeans(Masters2025[, radar_vars], na.rm = TRUE)
field_mean_2026 <- colMeans(Masters2026[, radar_vars], na.rm = TRUE)
radar_max <- ceiling(max(c(mcilroy_radar_2025, mcilroy_radar_2026,
field_mean_2025, field_mean_2026)) * 1.2)
radar_min <- floor(min(c(mcilroy_radar_2025, mcilroy_radar_2026,
field_mean_2025, field_mean_2026)) * 1.2)
radar_df <- as.data.frame(rbind(
rep(radar_max, 4),
rep(radar_min, 4),
mcilroy_radar_2025,
field_mean_2025,
mcilroy_radar_2026,
field_mean_2026))
colnames(radar_df) <- radar_labelsop <- par(mar = c(1, 1, 3, 1))
radarchart(radar_df[c(1, 2, 3, 4), ],
axistype = 1,
pcol = c("#006747", "gray60"),
pfcol = c(adjustcolor("#006747", alpha.f = 0.25),
adjustcolor("gray60", alpha.f = 0.15)),
plwd = 2.5,
cglcol = "gray80",
cglty = 1,
axislabcol = "gray40",
vlcex = 0.9,
title = "Strokes Gained Radar: McIlroy vs. Field — 2025 Masters")
legend("topright",
legend = c("McIlroy 2025", "Field Mean 2025"),
col = c("#006747", "gray60"),
lty = 1, lwd = 2.5,
bty = "n", cex = 0.85)op <- par(mar = c(1, 1, 3, 1))
radarchart(radar_df[c(1, 2, 5, 6), ],
axistype = 1,
pcol = c("#EFBF04", "gray60"),
pfcol = c(adjustcolor("#EFBF04", alpha.f = 0.25),
adjustcolor("gray60", alpha.f = 0.15)),
plwd = 2.5,
cglcol = "gray80",
cglty = 1,
axislabcol = "gray40",
vlcex = 0.9,
title = "Strokes Gained Radar: McIlroy vs. Field — 2026 Masters")
legend("topright",
legend = c("McIlroy 2026", "Field Mean 2026"),
col = c("#EFBF04", "gray60"),
lty = 1, lwd = 2.5,
bty = "n", cex = 0.85)op <- par(mar = c(1, 1, 3, 1))
radarchart(radar_df[c(1, 2, 3, 5), ],
axistype = 1,
pcol = c("#006747", "#EFBF04"),
pfcol = c(adjustcolor("#006747", alpha.f = 0.20),
adjustcolor("#EFBF04", alpha.f = 0.20)),
plwd = 2.5,
cglcol = "gray80",
cglty = 1,
axislabcol = "gray40",
vlcex = 0.9,
title = "McIlroy SG Components: 2025 vs. 2026 Masters")
legend("topright",
legend = c("McIlroy 2025", "McIlroy 2026"),
col = c("#006747", "#EFBF04"),
lty = 1, lwd = 2.5,
bty = "n",
cex = 0.85)The radar shots provide a visual summary of McIlroy’s SG profile relative to the field mean across 4 key categories each year. In both years, McIlroy’s shape enclosed the field mean, confirming he outperformed the average player in the Masters in every dimension. But the shape of his advantage differs between each year.
In 2025, the chart is asymmetric with a dramatic stretch towards Approach which reflects exceptional iron play. The putting axis barely extends beyond the field mean, once again confirming what the t-tests showed: he is an average putter.
In 2026, the shape was more balanced. The polygon extends evenly across all four axes with putting more and putting contributing meaningfully to the overall shape. The area is slightly smaller than 2025 meaning McIlroy’s advantage shrunk but the radar still shows a more well-rounded performance.
Pearson Correlation Analysis
sg_corr_vars <- c("sg_putt", "sg_arg", "sg_app", "sg_ott", "sg_t2g", "sg_bs", "sg_total")
sg_corr_labels <- c(
sg_putt = "Putting",
sg_arg = "Around Green",
sg_app = "Approach",
sg_ott = "Off the Tee",
sg_t2g = "Tee-to-Green",
sg_bs = "Ball Striking",
sg_total = "Total")
compute_correlations <- function(df, year) {
map_dfr(sg_corr_vars, function(var) {
complete_rows <- df |>
select(position, all_of(var)) |>
filter(!is.na(position), !is.na(.data[[var]]))
ct <- cor.test(complete_rows[[var]], complete_rows$position,
method = "pearson")
tibble(year = year,
variable = var,
label = sg_corr_labels[var],
correlation = round(ct$estimate, 3),
p_value = round(ct$p.value, 4),
significant = ifelse(ct$p.value < 0.05, "Yes *", "No"))})
}
corr_2025 <- compute_correlations(Masters2025, "2025")
corr_2026 <- compute_correlations(Masters2026, "2026")
corr_combined <- bind_rows(corr_2025, corr_2026) |>
arrange(variable, year)
print(corr_combined, n = Inf)# A tibble: 14 × 6
year variable label correlation p_value significant
<chr> <chr> <chr> <dbl> <dbl> <chr>
1 2025 sg_app Approach -0.474 0.0003 Yes *
2 2026 sg_app Approach -0.649 0 Yes *
3 2025 sg_arg Around Green -0.423 0.0016 Yes *
4 2026 sg_arg Around Green -0.427 0.0013 Yes *
5 2025 sg_bs Ball Striking -0.642 0 Yes *
6 2026 sg_bs Ball Striking -0.766 0 Yes *
7 2025 sg_ott Off the Tee -0.417 0.0019 Yes *
8 2026 sg_ott Off the Tee -0.491 0.0002 Yes *
9 2025 sg_putt Putting -0.371 0.0062 Yes *
10 2026 sg_putt Putting -0.268 0.0504 No
11 2025 sg_t2g Tee-to-Green -0.764 0 Yes *
12 2026 sg_t2g Tee-to-Green -0.848 0 Yes *
13 2025 sg_total Total -0.98 0 Yes *
14 2026 sg_total Total -0.977 0 Yes *
corr_plot_df <- corr_combined |>
mutate(label = factor(label, levels = sg_corr_labels[sg_corr_vars]),
sig_label = ifelse(significant == "Yes *", "*", ""))
ggplot(corr_plot_df, aes(x = label,
y = correlation,
fill = year)) +
geom_col(position = "dodge", alpha = 0.9, width = 0.65) +
geom_text(aes(label = sig_label,
y = correlation + ifelse(correlation >= 0, 0.03, -0.03)),
position = position_dodge(width = 0.65),
size = 5, vjust = 0) +
geom_hline(yintercept = 0,
linetype = "dashed",
color = "gray40") +
scale_fill_manual(values = c("2025" = "#006747", "2026" = "#EFBF04"),
name = "Year") +
labs(title = "Pearson Correlation: SG Components vs. Finishing Position",
subtitle = "Negative = higher SG predicts a better finish | * = p < .05",
x = NULL,
y = "Correlation with Finishing Position") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 30, hjust = 1))The Pearson correlation analysis correlates each SG category against finishing position for both years. The stronger the negative value, the more that skill drove the finishing order. SG:Total will be the best predictor of finishing position because where you rank in overall strokes gained is essentially where you will finish on the leaderboard.
SG:Tee to Green is the next strongest predictor for both years. It strengthened in 2026, suggesting non-putting ball control was a more decisive factor in separating the field. Ball striking follows a similar pattern, strengthening from -0.642 to -0.766.
SG:Approach saw the largest single-category shift between years. Players who hit greens well in 2026 were more likely to finish high. Putting was not a significant predictor of finishing position in 2026 like it was in 2025. That means putting barely influenced where players finished, making ball-striking the true separator.
Percentile Ranks
compute_percentiles <- function(masters_df, mcilroy_row, year) {
map_dfr(sg_corr_vars, function(var) {
field_vals <- masters_df[[var]]
mcilroy_val <- mcilroy_row[[var]]
pct <- round(mean(field_vals <= mcilroy_val, na.rm = TRUE) * 100, 1)
tibble(year = year,
variable = var,
label = sg_corr_labels[var],
mcilroy_sg = round(mcilroy_val, 3),
percentile = pct)})
}
pct_2025 <- compute_percentiles(Masters2025, mcilroy_2025, "2025")
pct_2026 <- compute_percentiles(Masters2026, mcilroy_2026, "2026")
pct_combined <- bind_rows(pct_2025, pct_2026) |>
arrange(variable, year)
print(pct_combined, n = Inf)# A tibble: 14 × 5
year variable label mcilroy_sg percentile
<chr> <chr> <chr> <dbl> <dbl>
1 2025 sg_app Approach 2.31 100
2 2026 sg_app Approach 0.66 75.8
3 2025 sg_arg Around Green 0.77 86.3
4 2026 sg_arg Around Green 1.39 96.7
5 2025 sg_bs Ball Striking 2.97 100
6 2026 sg_bs Ball Striking 1.58 90.1
7 2025 sg_ott Off the Tee 0.67 87.4
8 2026 sg_ott Off the Tee 0.91 89
9 2025 sg_putt Putting -0.31 41.1
10 2026 sg_putt Putting 0.59 79.1
11 2025 sg_t2g Tee-to-Green 3.74 100
12 2026 sg_t2g Tee-to-Green 2.97 97.8
13 2025 sg_total Total 3.43 100
14 2026 sg_total Total 3.55 100
pct_plot_df <- pct_combined |>
mutate(label = factor(label, levels = sg_corr_labels[sg_corr_vars]))
ggplot(pct_plot_df, aes(x = percentile,
y = label,
color = year)) +
geom_vline(xintercept = 50, linetype = "dashed", color = "#E31B23") +
geom_segment(
aes(x = 50,
xend = percentile,
y = label,
yend = label),
position = position_dodge(width = 0.6),
linewidth = 0.8) +
geom_point(size = 5,
position = position_dodge(width = 0.6)) +
geom_text(aes(label = paste0(percentile, "%")),
position = position_dodge(width = 0.6),
hjust = -0.35, size = 3, fontface = "bold") +
scale_color_manual(values = c("2025" = "#006747", "2026" = "#EFBF04"),
name = "Year") +
scale_x_continuous(limits = c(0, 110), breaks = c(0, 25, 50, 75, 100),
labels = c("0%", "25%", "50%", "75%", "100%")) +
labs(title = "McIlroy's Percentile Rank vs. Field by SG Category",
subtitle = "Dashed line = field median (50th percentile)",
x = "Percentile Rank",
y = NULL) +
theme_minimal()The percentile table and lollipop chart answer the question of where specifically McIlroy stood in the field and how much better he was. In 2025, he was in the 41st percentile of putting, below the field median. More than half the players in the tournament were better at putting. He won a Major while ranking in the bottom half of the field in putting, a testament to how his ball-striking carried him.
The 2026 lollipop chart shows all points clustered around the 75-100th percentile range, a profile with no real weakness. Approach is the sharpest reversal. He fell from the 100th percentile to 75.8%. Well above average but no longer the dominant or best player on the field. Given how approach was the strongest predictor of finishing position in 2026, this drop is notable.
Same Bag, New Round
One Sample T-Test
sg_test_vars <- c("sg_putt", "sg_arg", "sg_app", "sg_ott", "sg_t2g", "sg_bs", "sg_total")
run_ttest <- function(masters_df, mcilroy_row, year) {
purrr::map_dfr(sg_test_vars, function(var) {
field_vals <- masters_df[[var]][!is.na(masters_df[[var]])]
mcilroy_val <- mcilroy_row[[var]]
field_mean <- mean(field_vals, na.rm = TRUE)
field_sd <- sd(field_vals, na.rm = TRUE)
n <- length(field_vals)
t_result <- t.test(field_vals, mu = mcilroy_val)
tibble(year = year,
variable = var,
mcilroy_val = round(mcilroy_val, 3),
field_mean = round(field_mean, 3),
field_sd = round(field_sd, 3),
diff = round(mcilroy_val - field_mean, 3),
t_stat = round(t_result$statistic, 3),
p_value = round(t_result$p.value, 4),
significant = ifelse(t_result$p.value < 0.05, "Yes *", "No"))
})
}
ttest_2025 <- run_ttest(Masters2025, mcilroy_2025, "2025")
ttest_2026 <- run_ttest(Masters2026, mcilroy_2026, "2026")
ttest_combined <- bind_rows(ttest_2025, ttest_2026) |>
arrange(variable, year)
print(ttest_combined, n = Inf)# A tibble: 14 × 9
year variable mcilroy_val field_mean field_sd diff t_stat p_value
<chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 2025 sg_app 2.31 -0.169 1.22 2.48 -19.9 0
2 2026 sg_app 0.66 -0.198 1.24 0.858 -6.60 0
3 2025 sg_arg 0.77 -0.106 0.859 0.876 -9.94 0
4 2026 sg_arg 1.39 -0.101 0.874 1.49 -16.3 0
5 2025 sg_bs 2.97 -0.211 1.49 3.18 -20.8 0
6 2026 sg_bs 1.58 -0.326 1.90 1.91 -9.56 0
7 2025 sg_ott 0.67 -0.042 0.837 0.712 -8.29 0
8 2026 sg_ott 0.91 -0.129 1.04 1.04 -9.52 0
9 2025 sg_putt -0.31 -0.151 1.07 -0.159 1.46 0.149
10 2026 sg_putt 0.59 -0.165 1.12 0.755 -6.43 0
11 2025 sg_t2g 3.74 -0.318 1.76 4.06 -22.5 0
12 2026 sg_t2g 2.97 -0.427 2.11 3.40 -15.4 0
13 2025 sg_total 3.43 -0.468 1.99 3.90 -19.1 0
14 2026 sg_total 3.55 -0.594 2.47 4.14 -16.0 0
# ℹ 1 more variable: significant <chr>
sg_nice_labels <- c(
sg_putt = "Putting",
sg_arg = "Around Green",
sg_app = "Approach",
sg_ott = "Off the Tee",
sg_t2g = "Tee-to-Green",
sg_bs = "Ball Striking",
sg_total = "Total")
ttest_plot_df <- ttest_combined |>
mutate(variable_label = sg_nice_labels[variable],
variable_label = factor(variable_label,
levels = sg_nice_labels[sg_test_vars]))
ggplot(ttest_plot_df, aes(x = diff,
y = variable_label,
color = year)) +
geom_vline(xintercept = 0,
linetype = "dashed",
color = "gray50") +
geom_point(aes(shape = significant),
size = 4,
position = position_dodge(width = 0.5)) +
geom_text(data = ttest_plot_df |>
filter(significant == "Yes *"),
aes(label = "*"),
size = 6,
vjust = 0.3,
position = position_dodge(width = 0.5),
show.legend = FALSE) +
scale_color_manual(values = c("2025" = "#006747", "2026" = "#EFBF04"),
name = "Year") +
scale_shape_manual(values = c("Yes *" = 18, "No" = 16),
name = "Significant (p < .05)") +
labs(title = "McIlroy vs. Field: Strokes Gained Difference",
subtitle = "Diamond = statistically significant at p < .05",
x = "McIlroy SG − Field Mean",
y = NULL) +
theme_minimal() +
theme(legend.position = "right")The dot plot visualizes our tibble with the t-test results, making key findings readable. Every dot on the right of the dashed line means McIlroy outperformed the field mean. Nearly all the points are statistically significant with one exception: putting in 2025. This further confirms his performance was statistically indistinguishable from the average player.
Round by Round Scores
round_levels <- c("r1_score", "r2_score", "r3_score", "r4_score")
round_labels <- c("Round 1", "Round 2", "Round 3", "Round 4")
mcilroy_rounds_2025 <- mcilroy_2025 |>
pivot_longer(cols = all_of(round_levels),
names_to = "round",
values_to = "score") |>
mutate(round = factor(round,
levels = round_levels,
labels = round_labels))
mcilroy_rounds_2026 <- mcilroy_2026 |>
pivot_longer(cols = all_of(round_levels),
names_to = "round",
values_to = "score") |>
mutate(round = factor(round,
levels = round_levels,
labels = round_labels))
ggplot() +
geom_line(data = mcilroy_rounds_2025,
aes(x = round,
y = score,
color = "2025",
group = 1),
linewidth = 1.3) +
geom_point(data = mcilroy_rounds_2025,
aes(x = round,
y = score,
color = "2025"),
size = 4) +
geom_line(data = mcilroy_rounds_2026,
aes(x = round,
y = score, color = "2026",
group = 1),
linewidth = 1.3) +
geom_point(data = mcilroy_rounds_2026,
aes(x = round,
y = score,
color = "2026"),
size = 4) +
geom_hline(yintercept = 0,
linetype = "dashed",
color = "gray40") +
scale_color_manual(values = c("2025" = "#006747", "2026" = "#87B87F"), name = "Year") +
labs(title = "McIlroy's Round-by-Round Scores: 2025 vs. 2026",
x = NULL,
y = "Score (relative to par)") +
theme_minimal()The two scoring trajectories reveal starkly different paths with the same outcome: a win. In 2025, McIlroy opened with an even par but exploded in Round 2 with -6 and held steady in Round 3, creating a front-loaded performance that built his lead. His 2026 performance was inverted, coming out firing with Round 1 at -5, dipping to his best round yet in Round 2 with -7. He struggled back toward par in Rounds 3 and 4 but closed with a score just under par to off the field creating a win by one stroke difference.
SG:All Categories
all_sg_vars <- c("sg_putt", "sg_arg", "sg_app", "sg_ott", "sg_t2g", "sg_bs", "sg_total")
all_sg_labels <- c("Putting", "Around Green", "Approach", "Off Tee",
"Tee-to-Green", "Ball Striking", "Total")
mcilroy_all_2025 <- as.numeric(mcilroy_2025[, all_sg_vars])
mcilroy_all_2026 <- as.numeric(mcilroy_2026[, all_sg_vars])
radar_all <- as.data.frame(rbind(
rep(5, 7),
rep(-2, 7),
mcilroy_all_2025,
mcilroy_all_2026))
colnames(radar_all) <- all_sg_labels
rownames(radar_all) <- c("Max", "Min", "2025", "2026")op <- par(mar = c(2, 2, 3, 2))
radarchart(radar_all[c(1, 2, 3), ],
axistype = 1,
pcol = "#006747",
pfcol = adjustcolor("#006747", alpha.f = 0.3),
plwd = 2.5,
cglcol = "gray80",
cglty = 1,
axislabcol = "gray40",
vlcex = 0.9,
title = "McIlroy: All Strokes Gained Categories — 2025 Masters")
legend("topright",
legend = "McIlroy 2025",
col = "#006747",
lty = 1, lwd = 2.5,
bty = "n", cex = 0.85)op <- par(mar = c(2, 2, 3, 2))
radarchart(radar_all[c(1, 2, 4), ],
axistype = 1,
pcol = "#EFBF04",
pfcol = adjustcolor("#EFBF04", alpha.f = 0.3),
plwd = 2.5,
cglcol = "gray80",
cglty = 1,
axislabcol = "gray40",
vlcex = 0.9,
title = "McIlroy: All Strokes Gained Categories — 2026 Masters")
legend("topright",
legend = "McIlroy 2026",
col = "#EFBF04",
lty = 1, lwd = 2.5,
bt = "n", cex = 0.85)op <- par(mar = c(2, 2, 3, 2))
radarchart(radar_all[c(1, 2, 3, 4), ],
axistype = 1,
pcol = c("#006747", "#EFBF04"),
pfcol = c(adjustcolor("#006747", alpha.f = 0.2),
adjustcolor("#EFBF04", alpha.f = 0.2)),
plwd = 2.5,
cglcol = "gray80",
cglty = 1,
axislabcol = "gray40",
vlcex = 0.9,
title = "McIlroy: Strokes Gained 2025 vs. 2026 — All Categories")
legend("topright",
legend = c("McIlroy 2025", "McIlroy 2026"),
col = c("#006747", "#EFBF04"),
lty = 1, lwd = 2.5,
bty = "n", cex = 0.85)McIlroy’s performance across all seven categories are remarkably similar when placed on top of each other. The key differences are in two areas, the approach and putting. In his approach, the 2025 polygon extends extends further and reflects McIlroy’s 100th percentile iron play. Putting is the only space where there is a noticeable gap between the two shapes. This reflects his improvement in putting among the two years. Tee-to-Green and Total were identical between the years but Around Green and Off the Tee show improvement in 2026. This all reinforces how his ball-striking foundation remained constant across both championships while putting was what shifted meaningfully.
Walking Up 18
Walking Up 18 indicates the end of the round of golf, and a conclusion to the game.
top10_2025 <- Masters2025 |>
filter(position <= 10) |>
mutate(year = "2025",
is_mcilroy = str_detect(player_name, "McIlroy"))
top10_2026 <- Masters2026 |>
filter(position <= 10) |>
mutate(year = "2026",
is_mcilroy = str_detect(player_name, "McIlroy"))
top10_both <- bind_rows(top10_2025, top10_2026)r4_summary <- top10_both |>
group_by(year) |>
summarise(top10_r4_mean = round(mean(r4_score, na.rm = TRUE), 2),
top10_r4_sg_mean = round(mean(r4_sg_total, na.rm = TRUE), 2),
mcilroy_r4_score = r4_score[is_mcilroy],
mcilroy_r4_sg = r4_sg_total[is_mcilroy],
mcilroy_vs_top10_score = round(mcilroy_r4_score - top10_r4_mean, 2),
mcilroy_vs_top10_sg = round(mcilroy_r4_sg - top10_r4_sg_mean, 2))
print(r4_summary)# A tibble: 2 × 7
year top10_r4_mean top10_r4_sg_mean mcilroy_r4_score mcilroy_r4_sg
<chr> <dbl> <dbl> <dbl> <dbl>
1 2025 -0.91 0.99 1 -0.93
2 2026 -2.8 2.89 -1 1.09
# ℹ 2 more variables: mcilroy_vs_top10_score <dbl>, mcilroy_vs_top10_sg <dbl>
r4_sg_vars <- c("r4_sg_ott", "r4_sg_app", "r4_sg_arg", "r4_sg_putt")
r4_sg_labels <- c("Off the Tee", "Approach", "Around Green", "Putting")
top10_r4_long <- top10_both |>
select(player_name, year, is_mcilroy, all_of(r4_sg_vars)) |>
pivot_longer(cols = all_of(r4_sg_vars),
names_to = "component",
values_to = "sg") |>
mutate(component = factor(component,
levels = r4_sg_vars,
labels = r4_sg_labels))
mcilroy_r4_long <- top10_r4_long |> filter(is_mcilroy)
field_r4_long <- top10_r4_long |> filter(!is_mcilroy)
ggplot() +
geom_jitter(data = field_r4_long,
aes(x = sg,
y = component,
color = year),
width = 0, height = 0.15,
alpha = 0.5, size = 3) +
geom_point(
data = field_r4_long |>
group_by(year, component) |>
summarise(mean_sg = mean(sg, na.rm = TRUE), .groups = "drop"),
aes(x = mean_sg,
y = component,
color = year),
shape = 124, size = 8) +
geom_point(data = mcilroy_r4_long,
aes(x = sg,
y = component,
color = year),
shape = 18, size = 7) +
geom_text(data = mcilroy_r4_long,
aes(x = sg,
y = component,
label = paste0("McIlroy '", substr(year, 3, 4))),
vjust = -1, size = 3, fontface = "bold") +
geom_vline(xintercept = 0, linetype = "dashed", color = "#E31B23") +
scale_color_manual(values = c("2025" = "#006747", "2026" = "#EFBF04"),
name = "Year") +
facet_wrap(~ year) +
labs(title = "Round 4 SG Components: McIlroy vs. Top 10 Finishers",
subtitle = "Diamond = McIlroy | Bar = Top 10 mean | Dots = individual top 10 players",
x = "Strokes Gained (Round 4)",
y = NULL) +
theme_minimal() +
theme(legend.position = "none")The Round 4 summary table reveals our most important finding of the project. In 2025, the top 10 averaged -0.91 on Sunday with everyone finishing strong on the final stretch. McIlroy scored +1 and finished 1.91 stokes worse than his contenders on the last day. He won because he lead from Rounds 1 and 2 so he was able to absorb a difficult Sunday.
| 2025 | 2026 | |
|---|---|---|
| R4 SG Total | -0.93 | +1.09 |
| Top 10 R4 Mean | -0.91 | -2.80 |
| Top 10 R4 SG | -1.92 | -1.80 |
In 2026, the story was entirely different. The top 10 field had averaged -2.8 on Sunday giving us a collectively strong finish. McIlroy matched them at -1 and finished 1.8 strokes better than the field and ranked competitively within. His 2026 Sunday was far more composed relative to his peers.
Returning to the Approach
The Approach
Does McIlroy’s back-to-back Masters success reflect absolute improvement or relative dominance?
With the conclusion of Round 4 and these findings, we can directly answer our central question. McIlroy’s back-to-back success reflects on both absolute improvement and relative dominance but in different dimensions.
His ball-striking dominance represents genuine, stable elite-level ability that was present in both years. That is relative dominance: was was simply better than the field as Augusta, twice.
| 2025 | 2026 | |
|---|---|---|
| Total Score | -11 | -12 |
| SG Total | 3.43 | 3.55 |
The absolute improvement is concentrated in putting. Historically his worst skill in golf, he moved from the 41st percentile and statistical insignificant in 2025 to the 79th percentile and significance in 2026. Combined with the Round 4 data showing he closed more effectively relative to the field in 2026, the evidence suggests a player who was dominant in a more complete version of his game the second time around.
McIlroy evolved from a player carried by one or two dominant skills in 2025 to one whose second Green Jacket was earned through consistent excellence across the entire game.