Introduction

Competitive online games offer a useful environment for studying performance, decision-making, and strategy under pressure. League of Legends is especially interesting because every ranked match produces structured data that captures how teams gain advantages and convert them into wins. In this project, I focus on high-Diamond ranked games and look specifically at what happens in the first ten minutes of play. The goal is to understand which early-game performance metrics separate winning teams from losing teams and how those patterns line up with broader champion trends observed across the ranked ladder.

The primary dataset for this analysis comes from a Kaggle dataset containing high-Diamond ranked matches with detailed team-level statistics measured at the ten-minute mark. These variables include kills, deaths, assists, gold, experience, vision, and objectives taken. To add context to these match-level results, I supplement the analysis with a secondary dataset scraped from OP.GG, which provides champion win rates, pick rates, and ban rates across different roles. Together, these two datasets allow for a comparison between what wins games at a high level and how players choose champions within the broader meta.

Data Prep

lol_raw <- read_csv("high_diamond_ranked_10min.csv")
## Rows: 9879 Columns: 40
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (40): gameId, blueWins, blueWardsPlaced, blueWardsDestroyed, blueFirstBl...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
lol_clean <- lol_raw %>%
distinct() %>%
mutate(
blue_win = factor(blueWins, levels = c(0, 1), labels = c("loss", "win")),
blueKDA = if_else(blueDeaths == 0,
blueKills + blueAssists,
(blueKills + blueAssists) / blueDeaths),
redKDA = if_else(redDeaths == 0,
redKills + redAssists,
(redKills + redAssists) / redDeaths),
blueObjectives = blueDragons + blueHeralds + blueTowersDestroyed,
redObjectives  = redDragons + redHeralds + redTowersDestroyed
)

lol_long <- bind_rows(
lol_clean %>%
transmute(
gameId, team = "blue", win = blueWins,
KDA = blueKDA, goldPerMin = blueGoldPerMin,
wardsPlaced = blueWardsPlaced, objectives = blueObjectives
),
lol_clean %>%
transmute(
gameId, team = "red", win = 1 - blueWins,
KDA = redKDA, goldPerMin = redGoldPerMin,
wardsPlaced = redWardsPlaced, objectives = redObjectives
)
) %>%
mutate(win = factor(win, levels = c(0, 1), labels = c("loss", "win")))

Early Fighting and KDA

ggplot(lol_long, aes(x = win, y = KDA, fill = win)) +
geom_boxplot() +
labs(title = "KDA by Win/Loss (10-minute snapshot)", x = "Outcome", y = "KDA") +
theme_minimal()

Looking at the KDA chart, the difference between winning and losing teams jumps out immediately. Teams that win tend to have noticeably higher KDA values even just ten minutes into the game, while losing teams are clustered much lower with far less upside. This suggests that early skirmishes at this level tend to be decisive rather than chaotic, with winning teams converting fights cleanly instead of trading evenly.

Gold Per Minute and Lane Control

ggplot(lol_long, aes(x = win, y = goldPerMin, fill = win)) +
geom_violin(alpha = 0.6) +
geom_boxplot(width = 0.12) +
labs(title = "Gold per Minute by Win/Loss", x = "Outcome", y = "Gold per Minute") +
theme_minimal()

Gold per minute shows a clear separation between wins and losses. Winning teams consistently earn more gold per minute, which points to stronger lane control and more efficient farming. Even when losing teams have occasional high outliers, the overall pattern shows that sustained economic pressure is a major driver of success in high-Diamond games.

Objective Control by 10 Minutes

ggplot(lol_long, aes(x = objectives, fill = win)) +
geom_histogram(position = "dodge", bins = 6) +
labs(
title = "Objectives Taken by 10 Minutes vs Outcome",
x = "Objectives (Dragons + Heralds + Towers)", y = "Teams"
) +
theme_minimal()

Teams that secure multiple objectives early are far more likely to win. When a team takes two or more objectives by ten minutes, the game often begins to snowball quickly. This reinforces the idea that high-level teams consistently turn early pressure into tangible map advantages rather than allowing the game to stall.

Early Gold Difference

ggplot(lol_clean, aes(x = blueTotalGold - redTotalGold, fill = blue_win)) +
geom_density(alpha = 0.6) +
labs(
title = "Gold Difference at 10 Minutes by Game Outcome",
x = "Gold Difference (Blue - Red)", y = "Density"
) +
theme_minimal()

The gold difference distribution shows that games begin separating very early. Winning teams are usually ahead in gold by ten minutes, while losing teams are almost always playing from behind. This suggests that comebacks are relatively rare at this level and that early leads tend to compound over time.

Vision Control

ggplot(lol_long, aes(x = win, y = wardsPlaced, fill = win)) +
geom_boxplot() +
labs(title = "Vision Control by Win/Loss", x = "Outcome", y = "Wards Placed") +
theme_minimal()

Winning teams place slightly more wards on average and also show a wider upper range in vision investment. While vision alone does not guarantee a win, this supports the idea that better teams consistently manage information as part of their overall macro strategy.

OP.GG Champion Meta (Secondary Source via Web Scrape)

opgg_clean <- read_csv("opgg_clean.csv")
## Rows: 310 Columns: 7
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (2): champion, position
## dbl  (4): rank, win_rate, pick_rate, ban_rate
## dttm (1): scraped_at
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
opgg_clean %>%
filter(position != "all") %>%
ggplot(aes(x = pick_rate, y = win_rate)) +
geom_point(alpha = 0.6) +
facet_wrap(~ position) +
labs(
title = "OP.GG: Win Rate vs Pick Rate by Position",
x = "Pick Rate (%)", y = "Win Rate (%)"
) +
theme_minimal()
## Warning: Removed 5 rows containing missing values or values outside the scale range
## (`geom_point()`).

opgg_clean %>%
filter(position == "all") %>%
slice_max(pick_rate, n = 15) %>%
ggplot(aes(x = reorder(champion, pick_rate), y = pick_rate)) +
geom_col() +
coord_flip() +
labs(
title = "OP.GG: Top 15 Champions by Pick Rate",
x = "Champion", y = "Pick Rate (%)"
) +
theme_minimal()

opgg_clean %>%
filter(position == "all") %>%
ggplot(aes(x = ban_rate, y = win_rate)) +
geom_point(alpha = 0.6) +
geom_smooth(se = FALSE) +
labs(
title = "OP.GG: Ban Rate vs Win Rate",
x = "Ban Rate (%)", y = "Win Rate (%)"
) +
theme_minimal()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## Warning: Removed 1 row containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_point()`).

The OP.GG data adds important context to the match-level results. Highly picked champions are not always the highest win-rate options, especially in bottom and support roles, where comfort and reliability appear to matter more than raw power. Ban rates also do not perfectly track win rates, which suggests that perceived threat and frustration influence player behavior alongside actual performance.

Conclusion

Taken together, these results paint a consistent picture of high-Diamond League of Legends play. Games are largely decided early through gold generation, objective control, and clean execution, and teams that fall behind early rarely recover. The OP.GG data supports these findings by showing that players tend to favor champions that enable stable early-game pressure and reliable macro play, even if those champions are not always the strongest on paper. By combining match-level performance data with broader meta trends, this analysis helps explain not just who wins games, but why those wins happen.