This report applies Market Basket Analysis (MBA), a data mining technique traditionally used in retail to study product co-occurrence in shopping carts, to uncover hidden patterns within successful team compositions in League of Legends Season 11.
The core analytical concept treats each winning team composition as a single transaction: the five champions played by the winning team in a match form a “basket,” and individual champions are the “items” inside it. The goal is to identify sets of champions that statistically appear together most frequently in matches resulting in a victory.
The analysis is based on the League of Legends High Elo Ranked Team Comp S11 dataset sourced from Kaggle (leetolo, available at kaggle.com/datasets/leetolo/league-of-legend-high-elo-ranked-team-comp-s11). Focusing on High Elo data is methodologically important, it filters out the noise of casual play and captures synergies that are deliberately constructed by the top-tier player base, making the co-occurrence patterns more likely to reflect intentional strategic decisions rather than coincidence.
To provide a comprehensive view of team composition structure, three complementary techniques are applied. Apriori association rules extract directional synergies between champions (e.g., teams that pick champion A are significantly more likely to also pick champion B). A pair synergy network provides a graph-based visualization of the strongest and most stable pairwise relationships across all winning teams. Class Association Rules (CAR) identify champion combinations where the consequent is always Victory, highlighting the picks that most consistently anchor a winning composition.
Throughout the analysis, it is important to note that MBA is a descriptive method, it discovers co-occurrence patterns and correlations, but does not imply direct causality. The synergies identified reflect the Season 11 meta: the strategies and champion combinations that top players converged on as most effective during that period.
This report is an independent analysis that complements the companion clustering project, offering an alternative, rule-based view of champion synergies. (Champion Clustering and Dimension Reduction, available at https://rpubs.com/Marta_B/Lol_Champion_ClustersiDimension), which identified four stable champion archetypes based on base statistics.
The clustering project asks what the archetypes look like in terms of champions and indicators; this report asks how champions interact—i.e., which combinations recur in winning teams and whether they map onto the same archetype structure.
library(readr)
library(dplyr)
library(tidyr)
library(stringr)
library(knitr)
library(ggplot2)
library(ggraph)
library(tidygraph)
library(viridis)
library(arules)
library(arulesViz)
The dataset contains 2,901 winning team transactions built from a total of 2,901 matches (T1 won 630, T2 won 2,271). Every transaction consists of exactly 5 champions, confirmed by the basket size diagnostic, covering 153 unique champions across the entire pool. The imbalance between T1 and T2 wins is expected in this data format and does not affect the analysis, since we extract the winning team regardless of which side they played on.
df <- read_delim("s11.csv", delim = ";", show_col_types = FALSE)
t1_cols <- paste0("team_1__", str_pad(1:5, 3, pad = "0"))
t2_cols <- paste0("team_2__", str_pad(1:5, 3, pad = "0"))
all_champ_cols <- c(t1_cols, t2_cols)
stopifnot(all(t1_cols %in% names(df)), all(t2_cols %in% names(df)))
stopifnot("result" %in% names(df))
df <- df %>%
mutate(
match_id = row_number(),
result = str_to_lower(str_trim(result)),
winner_team = case_when(
result == "victory" ~ "T1",
result == "defeat" ~ "T2",
TRUE ~ NA_character_
)
)
kable(
as.data.frame(table(df$winner_team, useNA = "ifany")),
col.names = c("winner_team", "n_matches")
)
| winner_team | n_matches |
|---|---|
| T1 | 630 |
| T2 | 2271 |
long <- df %>%
select(match_id, winner_team, all_of(all_champ_cols)) %>%
pivot_longer(cols = all_of(all_champ_cols), names_to = "slot", values_to = "champion") %>%
mutate(
team = if_else(str_detect(slot, "^team_1__"), "T1", "T2"),
champion = str_squish(champion)
) %>%
filter(!is.na(champion), champion != "") %>%
distinct(match_id, winner_team, team, champion)
winners_long <- long %>%
filter(team == winner_team) %>%
select(match_id, champion) %>%
distinct()
trans_list <- split(winners_long$champion, winners_long$match_id)
transactions <- as(trans_list, "transactions")
transactions
## transactions in sparse format with
## 2901 transactions (rows) and
## 153 items (columns)
kable(data.frame(
n_transactions = length(transactions),
n_unique_champions = n_distinct(winners_long$champion)
))
| n_transactions | n_unique_champions |
|---|---|
| 2901 | 153 |
basket_sizes <- winners_long %>%
count(match_id, name = "basket_size") %>%
count(basket_size, name = "n_transactions")
kable(basket_sizes, col.names = c("basket_size", "n_transactions"))
| basket_size | n_transactions |
|---|---|
| 5 | 2901 |
A note on team imbalance The dataset shows a significant disproportion between T1 wins (630) and T2 wins (2,271). This likely reflects the data collection perspective — matches are recorded from the viewpoint of a single player, where “result” indicates whether that player’s team won, and team assignment is not randomized. Since both T1 and T2 winners are pooled into the same transaction set regardless of side, this imbalance does not affect the MBA results — every winning composition contributes equally to the analysis irrespective of which column it was stored in.
A note on support levels With 153 unique champions and fixed basket sizes of exactly 5, the combinatorial space is extremely large. Even the strongest synergy in the dataset (Aphelios–Thresh, support ≈ 2%) appears in only 58 of 2,901 matches. This is expected given the champion pool size and the diversity of high-elo play, but it means all rules in this analysis describe relatively rare patterns. Lift and confidence are therefore more informative than raw support for evaluating synergy strength.
We aim for a readable set of rules: -
support = 0.01 ensures a rule appears in at least ~1% of
winner baskets (stability), - confidence = 0.2 reduces
noise and keeps the rule set compact.
rules <- apriori(
transactions,
parameter = list(supp = 0.01, conf = 0.2, minlen = 2, maxlen = 3)
)
## Apriori
##
## Parameter specification:
## confidence minval smax arem aval originalSupport maxtime support minlen
## 0.2 0.1 1 none FALSE TRUE 5 0.01 2
## maxlen target ext
## 3 rules TRUE
##
## Algorithmic control:
## filter tree heap memopt load sort verbose
## 0.1 TRUE TRUE FALSE TRUE 2 TRUE
##
## Absolute minimum support count: 29
##
## set item appearances ...[0 item(s)] done [0.00s].
## set transactions ...[153 item(s), 2901 transaction(s)] done [0.00s].
## sorting and recoding items ... [97 item(s)] done [0.00s].
## creating transaction tree ... done [0.00s].
## checking subsets of size 1 2 3
## done [0.00s].
## writing ... [61 rule(s)] done [0.00s].
## creating S4 object ... done [0.00s].
rules <- rules[!is.redundant(rules)]
rules_sorted <- sort(rules, by = "lift", decreasing = TRUE)
# keep only "positive synergy" rules
rules_sorted <- subset(rules_sorted, subset = lift > 1)
kable(data.frame(n_rules = length(rules_sorted)))
| n_rules |
|---|
| 60 |
inspect(head(rules_sorted, 20))
## lhs rhs support confidence coverage lift count
## [1] {Aphelios} => {Thresh} 0.01999311 0.3815789 0.05239573 4.358112 58
## [2] {Thresh} => {Aphelios} 0.01999311 0.2283465 0.08755602 4.358112 58
## [3] {Yone} => {Taliyah} 0.02068252 0.2343750 0.08824543 2.394091 60
## [4] {Taliyah} => {Yone} 0.02068252 0.2112676 0.09789728 2.394091 60
## [5] {Renekton} => {Taliyah} 0.01482248 0.2216495 0.06687349 2.264103 43
## [6] {Ornn} => {Graves} 0.01103068 0.3764706 0.02930024 2.056763 32
## [7] {Lee Sin} => {Akali} 0.01413306 0.2369942 0.05963461 2.016188 41
## [8] {Syndra} => {Graves} 0.02171665 0.3559322 0.06101344 1.944556 63
## [9] {Samira} => {Alistar} 0.01758014 0.2161017 0.08135126 1.746270 51
## [10] {Lee Sin} => {Gragas} 0.01206481 0.2023121 0.05963461 1.741565 35
## [11] {Viktor} => {Graves} 0.01344364 0.2977099 0.04515684 1.626472 39
## [12] {Lillia} => {Lucian} 0.02309548 0.2392857 0.09651844 1.581248 67
## [13] {Malphite} => {Graves} 0.01482248 0.2828947 0.05239573 1.545532 43
## [14] {Taliyah} => {Lucian} 0.02275078 0.2323944 0.09789728 1.535709 66
## [15] {Nidalee} => {Lucian} 0.01689073 0.2158590 0.07824888 1.426440 49
## [16] {Sett} => {Graves} 0.02826612 0.2538700 0.11134092 1.386962 82
## [17] {Pantheon} => {Kai'Sa} 0.02688728 0.2689655 0.09996553 1.383456 78
## [18] {Viktor} => {Kai'Sa} 0.01206481 0.2671756 0.04515684 1.374249 35
## [19] {Malphite} => {Jhin} 0.01447777 0.2763158 0.05239573 1.344953 42
## [20] {Ekko} => {Lucian} 0.01344364 0.2031250 0.06618407 1.342291 39
Each association rule takes the form {LHS} ⇒ {RHS} and is described by three measures:
Worked example: Rule {Aphelios} ⇒ {Thresh} has support = 0.020, confidence = 0.382, and lift = 4.36. This means: this pair appeared in 2% of all winning baskets (58 out of 2,901 matches); in 38% of matches where Aphelios was on the winning team, Thresh was also present; and this co-occurrence is 4.36 times more frequent than would be expected if the two champions were picked independently of each other. The high lift confirms this is a genuine strategic synergy, not a coincidence driven by the individual popularity of either champion.
rule_sizes <- table(size(lhs(rules_sorted)) + size(rhs(rules_sorted)))
kable(as.data.frame(rule_sizes), col.names = c("rule_size_total_items", "n_rules"))
| rule_size_total_items | n_rules |
|---|---|
| 2 | 60 |
The Apriori algorithm produced 60 non-redundant rules with positive lift (lift > 1), all of them pair rules (rule size = 2), meaning no three-champion combinations met the minimum support threshold of 1%. This is consistent with the combinatorial difficulty of the problem, with 153 champions and 5-champion baskets, three-way co-occurrences are naturally rare even in a dataset of nearly 3,000 matches.
The top rules by lift are dominated by the Aphelios-Thresh pairing (lift = 4.36, count = 58), which is by far the strongest synergy in the dataset. This duo is well-known in Season 11 as a bot lane combination, Aphelios is a high-damage but immobile marksman who benefits enormously from Thresh’s ability to peel and engage, making their co-occurrence in winning teams far higher than their individual popularity would predict. The second strongest pattern is Yone–Taliyah (lift = 2.39, count = 60), a mid-lane combination where Taliyah’s Weaver’s Wall trap enables Yone’s engage, representing a clear strategic synergy rather than coincidence. Renekton–Taliyah (lift = 2.26) extends this pattern further, suggesting a lane-dominant early-game composition built around Taliyah as a playmaking anchor. Lower in the list, Graves appears multiple times as an RHS (paired with Ornn, Syndra, Malphite, Sett, Viktor), suggesting it functions as a flexible jungle pick that complements a variety of team compositions.
plot(head(rules_sorted, 20), method = "grouped")
The grouped plot confirms the structure of the rule set visually. The
Aphelios-Thresh pair stands out clearly in the top-left as the largest
and darkest circle, highest support and highest lift simultaneously,
confirming it is both frequent and genuinely synergistic rather than
merely popular. The remaining rules scatter across lower lift values
with smaller dot sizes, indicating that most synergies in the dataset
are moderate in strength and relatively rare in absolute frequency. The
absence of high-support rules beyond the Aphelios–Thresh pair suggests
that Season 11 winner compositions were diverse rather than dominated by
a single recurring team structure.
This network is not directional. It shows which pairs co-occur in winner baskets more than expected.
We rank pairs with:
\[ score = lift \cdot \log(1+n) \]
lift captures “beyond popularity” synergy,log(1+n) rewards pairs that appear more often
(reliability).M <- n_distinct(winners_long$match_id)
supp_1 <- winners_long %>%
distinct(match_id, champion) %>%
count(champion, name = "nA") %>%
mutate(suppA = nA / M)
winner_pairs <- winners_long %>%
inner_join(winners_long, by = "match_id", relationship = "many-to-many") %>%
filter(champion.x < champion.y) %>%
transmute(a = champion.x, b = champion.y) %>%
count(a, b, name = "n", sort = TRUE)
pairs_metrics <- winner_pairs %>%
mutate(suppAB = n / M) %>%
left_join(supp_1 %>% select(a = champion, nA, suppA), by = "a") %>%
left_join(supp_1 %>% select(b = champion, nB = nA, suppB = suppA), by = "b") %>%
mutate(
lift = suppAB / (suppA * suppB),
score = lift * log1p(n)
)
# stability filters
min_n_pair <- 30
min_n_single <- 50
pairs_metrics_stable <- pairs_metrics %>%
filter(n >= min_n_pair, nA >= min_n_single, nB >= min_n_single) %>%
arrange(desc(score))
kable(head(pairs_metrics_stable, 20))
| a | b | n | suppAB | nA | suppA | nB | suppB | lift | score |
|---|---|---|---|---|---|---|---|---|---|
| Aphelios | Thresh | 58 | 0.0199931 | 152 | 0.0523957 | 254 | 0.0875560 | 4.358112 | 17.770366 |
| Taliyah | Yone | 60 | 0.0206825 | 284 | 0.0978973 | 256 | 0.0882454 | 2.394091 | 9.841807 |
| Renekton | Taliyah | 43 | 0.0148225 | 194 | 0.0668735 | 284 | 0.0978973 | 2.264103 | 8.567794 |
| Graves | Syndra | 63 | 0.0217166 | 531 | 0.1830403 | 177 | 0.0610134 | 1.944556 | 8.087182 |
| Nidalee | Renekton | 33 | 0.0113754 | 227 | 0.0782489 | 194 | 0.0668735 | 2.173873 | 7.665858 |
| Akali | Lee Sin | 41 | 0.0141331 | 341 | 0.1175457 | 173 | 0.0596346 | 2.016188 | 7.535846 |
| Graves | Ornn | 32 | 0.0110307 | 531 | 0.1830403 | 85 | 0.0293002 | 2.056763 | 7.191488 |
| Alistar | Samira | 51 | 0.0175801 | 359 | 0.1237504 | 236 | 0.0813513 | 1.746270 | 6.899939 |
| Maokai | Nidalee | 34 | 0.0117201 | 230 | 0.0792830 | 227 | 0.0782489 | 1.889178 | 6.716686 |
| Lillia | Lucian | 67 | 0.0230955 | 280 | 0.0965184 | 439 | 0.1513271 | 1.581248 | 6.672088 |
| Lucian | Taliyah | 66 | 0.0227508 | 439 | 0.1513271 | 284 | 0.0978973 | 1.535709 | 6.457182 |
| Gragas | Yone | 48 | 0.0165460 | 337 | 0.1161668 | 256 | 0.0882454 | 1.614058 | 6.281623 |
| Gragas | Lee Sin | 35 | 0.0120648 | 337 | 0.1161668 | 173 | 0.0596346 | 1.741565 | 6.240932 |
| Lillia | Yone | 41 | 0.0141331 | 280 | 0.0965184 | 256 | 0.0882454 | 1.659333 | 6.202039 |
| Graves | Sett | 82 | 0.0282661 | 531 | 0.1830403 | 323 | 0.1113409 | 1.386962 | 6.128764 |
| Kai’Sa | Pantheon | 78 | 0.0268873 | 564 | 0.1944157 | 290 | 0.0999655 | 1.383456 | 6.044937 |
| Graves | Viktor | 39 | 0.0134436 | 531 | 0.1830403 | 131 | 0.0451568 | 1.626472 | 5.999858 |
| Kai’Sa | Leona | 93 | 0.0320579 | 564 | 0.1944157 | 370 | 0.1275422 | 1.292855 | 5.873822 |
| Graves | Malphite | 43 | 0.0148225 | 531 | 0.1830403 | 152 | 0.0523957 | 1.545532 | 5.848587 |
| Samira | Taliyah | 37 | 0.0127542 | 236 | 0.0813513 | 284 | 0.0978973 | 1.601471 | 5.825489 |
set.seed(1)
top_edges <- pairs_metrics_stable %>%
slice_head(n = 50) %>%
transmute(from = a, to = b, weight = lift)
g <- as_tbl_graph(top_edges, directed = FALSE) %>%
mutate(
community = as.factor(group_louvain()),
importance = centrality_degree()
)
ggraph(g, layout = "fr") +
geom_edge_link(aes(width = weight), alpha = 0.25, color = "gray50") +
geom_node_point(aes(color = community, size = importance)) +
geom_node_text(aes(label = name), repel = TRUE, size = 4) +
scale_edge_width(range = c(0.5, 3)) +
theme_void(base_size = 12) +
labs(
title = "Winner synergy network (pair lift)",
subtitle = paste0("Filters: n >= ", min_n_pair, ", nA/nB >= ", min_n_single,
" | edges: top 50 by score")
)
The network reveals six communities of co-occurring champions in winning
teams. The most central node by degree is Kai’Sa (green community,
largest circle), connected to Pantheon, Zoe, Leona, Viktor, and Sett,
suggesting she functioned as a highly adaptable ADC that paired well
with diverse supports and solo laners. Graves (pink/red community) is
the second most connected node, linking to Syndra, Ornn, Sett, Viktor,
Malphite, and others consistent with the Apriori rules finding Graves as
a recurring RHS. The Aphelios-Thresh pair appears as a separate isolated
community (pink, far right) with only a single edge between them and no
connections to the main graph, which reflects their exceptionally high
mutual lift but relative independence from the broader synergy network.
The gold community (Taliyah, Yone, Renekton, Lucian, Nidalee, Lillia)
represents a cluster of mobile, map-controlling champions that
frequently co-occurred, a pattern consistent with the Season 11 meta
favoring fast-paced, objective-focused compositions.
Fix applied: the class label must be an item
without ‘=’ (arulesCBA/CAR parsing is strict). So we
use CLASS_Victory and CLASS_Defeat instead of
Result=Victory.
all_matches_car <- df %>%
select(match_id, winner_team, all_of(all_champ_cols)) %>%
pivot_longer(cols = all_of(all_champ_cols), names_to = "slot", values_to = "champion") %>%
mutate(
team_label = if_else(str_detect(slot, "^team_1__"), "T1", "T2"),
class_item = if_else(team_label == winner_team, "CLASS_Victory", "CLASS_Defeat"),
champion = str_squish(champion)
) %>%
filter(!is.na(champion), champion != "") %>%
distinct(match_id, team_label, champion, class_item)
basket_id <- paste(all_matches_car$match_id, all_matches_car$team_label, sep = "__")
trans_list_all <- split(all_matches_car$champion, basket_id)
class_list_all <- split(all_matches_car$class_item, basket_id)
final_list_all <- mapply(
c,
trans_list_all,
lapply(class_list_all, function(x) unique(x)[1]),
SIMPLIFY = FALSE
)
transactions_all <- as(final_list_all, "transactions")
rules_victory <- apriori(
transactions_all,
parameter = list(supp = 0.005, conf = 0.6, minlen = 2, maxlen = 3),
appearance = list(rhs = "CLASS_Victory", default = "lhs")
)
## Apriori
##
## Parameter specification:
## confidence minval smax arem aval originalSupport maxtime support minlen
## 0.6 0.1 1 none FALSE TRUE 5 0.005 2
## maxlen target ext
## 3 rules TRUE
##
## Algorithmic control:
## filter tree heap memopt load sort verbose
## 0.1 TRUE TRUE FALSE TRUE 2 TRUE
##
## Absolute minimum support count: 29
##
## set item appearances ...[1 item(s)] done [0.00s].
## set transactions ...[155 item(s), 5802 transaction(s)] done [0.00s].
## sorting and recoding items ... [121 item(s)] done [0.00s].
## creating transaction tree ... done [0.00s].
## checking subsets of size 1 2 3
## done [0.00s].
## writing ... [18 rule(s)] done [0.00s].
## creating S4 object ... done [0.00s].
rules_victory <- rules_victory[!is.redundant(rules_victory)]
rules_victory_sorted <- sort(rules_victory, by = "lift", decreasing = TRUE)
kable(data.frame(n_rules_victory = length(rules_victory_sorted)))
| n_rules_victory |
|---|
| 18 |
inspect(head(rules_victory_sorted, 15))
## lhs rhs support confidence
## [1] {Sett, Zoe} => {CLASS_Victory} 0.005342985 0.7209302
## [2] {Jhin, Malphite} => {CLASS_Victory} 0.007238883 0.6774194
## [3] {Akali, Hecarim} => {CLASS_Victory} 0.005170631 0.6521739
## [4] {Kayle} => {CLASS_Victory} 0.007066529 0.6507937
## [5] {Alistar, Miss Fortune} => {CLASS_Victory} 0.007411238 0.6417910
## [6] {Alistar, Zoe} => {CLASS_Victory} 0.005860048 0.6415094
## [7] {Maokai, Nidalee} => {CLASS_Victory} 0.005860048 0.6415094
## [8] {Rek'Sai} => {CLASS_Victory} 0.011892451 0.6388889
## [9] {Kai'Sa, Zoe} => {CLASS_Victory} 0.007755946 0.6338028
## [10] {Graves, Sett} => {CLASS_Victory} 0.014133058 0.6307692
## [11] {Kai'Sa, Viktor} => {CLASS_Victory} 0.006032403 0.6250000
## [12] {Swain} => {CLASS_Victory} 0.006204757 0.6206897
## [13] {Irelia, Jhin} => {CLASS_Victory} 0.008445364 0.6202532
## [14] {Jhin, Sett} => {CLASS_Victory} 0.012409514 0.6153846
## [15] {Ezreal, Lucian} => {CLASS_Victory} 0.006204757 0.6101695
## coverage lift count
## [1] 0.007411238 1.441860 31
## [2] 0.010685970 1.354839 42
## [3] 0.007928301 1.304348 30
## [4] 0.010858325 1.301587 41
## [5] 0.011547742 1.283582 43
## [6] 0.009134781 1.283019 34
## [7] 0.009134781 1.283019 34
## [8] 0.018614271 1.277778 69
## [9] 0.012237160 1.267606 45
## [10] 0.022406067 1.261538 82
## [11] 0.009651844 1.250000 35
## [12] 0.009996553 1.241379 36
## [13] 0.013615994 1.240506 49
## [14] 0.020165460 1.230769 72
## [15] 0.010168907 1.220339 36
top_k <- min(12, length(rules_victory_sorted))
if (top_k > 0) {
plot(head(rules_victory_sorted, top_k), method = "graph", engine = "htmlwidget")
}
The CAR analysis produced 18 rules pointing to CLASS_Victory with confidence ≥ 0.60. The strongest rule is {Sett, Zoe} ⇒ CLASS_Victory (confidence = 0.72, lift = 1.44), meaning that in 72% of all matches where this combination appeared on a team, that team won. {Jhin, Malphite} (confidence = 0.68) and {Akali, Hecarim} (0.65) follow closely. Notably, Kayle appears as a solo single-champion rule (confidence = 0.65), suggesting she was a strong pick in this patch independently of her teammates. Several Zoe rules appear (with Alistar, Kai’Sa, Sett), pointing to Zoe as a particularly win-predictive champion when paired correctly. Rek’Sai also appears as a solo rule (confidence = 0.64), reinforcing the delta support finding in Section 8. All rules have lift modestly above 1 (range 1.22–1.44), confirming these are genuine win associations beyond the baseline 50% win rate, but also reminding us this is a descriptive, not causal, analysis.
This section separates two distinct questions: which champions are popular among winners (MBA view), and which champions actually discriminate between winning and losing teams (CAR/delta view).
# Transaction-level supports per class (Victory vs Defeat)
tx_meta <- all_matches_car %>%
distinct(match_id, team_label, class_item) %>%
count(class_item, name = "n_transactions")
N_v <- tx_meta$n_transactions[tx_meta$class_item == "CLASS_Victory"]
N_d <- tx_meta$n_transactions[tx_meta$class_item == "CLASS_Defeat"]
champ_by_class <- all_matches_car %>%
distinct(match_id, team_label, class_item, champion) %>%
count(class_item, champion, name = "n_tx") %>%
tidyr::pivot_wider(names_from = class_item, values_from = n_tx, values_fill = 0) %>%
mutate(
supp_victory = CLASS_Victory / N_v,
supp_defeat = CLASS_Defeat / N_d,
delta_supp = supp_victory - supp_defeat,
rr_supp = (supp_victory + 1e-9) / (supp_defeat + 1e-9)
) %>%
arrange(desc(delta_supp))
top_delta <- champ_by_class %>%
slice_head(n = 20) %>%
transmute(
champion,
supp_victory = scales::percent(supp_victory, accuracy = 0.1),
supp_defeat = scales::percent(supp_defeat, accuracy = 0.1),
delta_supp = scales::percent(delta_supp, accuracy = 0.1),
rr_supp = round(rr_supp, 2)
)
cat("Champions most associated with winning (Δ support: Victory - Defeat)\n\n")
Champions most associated with winning (Δ support: Victory - Defeat)
knitr::kable(top_delta, align = "lrrrr")
| champion | supp_victory | supp_defeat | delta_supp | rr_supp |
|---|---|---|---|---|
| Alistar | 12.4% | 10.7% | 1.7% | 1.15 |
| Jhin | 20.5% | 18.9% | 1.7% | 1.09 |
| Sett | 11.1% | 9.7% | 1.4% | 1.15 |
| Lucian | 15.1% | 14.0% | 1.1% | 1.08 |
| Rek’Sai | 2.4% | 1.3% | 1.0% | 1.77 |
| Gragas | 11.6% | 10.6% | 1.0% | 1.09 |
| Nidalee | 7.8% | 6.9% | 1.0% | 1.14 |
| Kai’Sa | 19.4% | 18.5% | 0.9% | 1.05 |
| Irelia | 7.1% | 6.3% | 0.9% | 1.14 |
| Hecarim | 5.9% | 5.0% | 0.8% | 1.16 |
| Volibear | 2.4% | 1.8% | 0.7% | 1.37 |
| Kayle | 1.4% | 0.8% | 0.7% | 1.86 |
| Janna | 2.5% | 1.9% | 0.6% | 1.33 |
| Maokai | 7.9% | 7.4% | 0.6% | 1.07 |
| Sona | 0.9% | 0.4% | 0.6% | 2.45 |
| Nautilus | 3.4% | 2.9% | 0.5% | 1.18 |
| Olaf | 6.0% | 5.5% | 0.5% | 1.09 |
| Tryndamere | 1.0% | 0.4% | 0.5% | 2.15 |
| Swain | 1.2% | 0.8% | 0.5% | 1.64 |
| Viktor | 4.5% | 4.1% | 0.4% | 1.11 |
plot_df <- champ_by_class %>%
slice_head(n = 20) %>%
mutate(champion = forcats::fct_reorder(champion, delta_supp))
ggplot(plot_df, aes(x = champion, y = delta_supp, fill = delta_supp)) +
geom_col() +
coord_flip() +
scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
scale_fill_viridis_c(option = "C") +
labs(
title = "Champions that differentiate the outcome (Victory vs Defeat)",
subtitle = "Δ support = support(Victory) − support(Defeat) at the team-transaction level",
x = NULL,
y = "Δ support"
) +
theme_minimal()
Looking at the graph, Alistar and Jhin top the delta support ranking
(both Δ = 1.7%), but their interpretation differs, Jhin has very high
absolute support in both victory (20.5%) and defeat (18.9%), meaning he
is simply one of the most-played champions in the dataset and the small
delta reflects a modest win rate edge. Alistar’s story is more
interesting: his lower absolute frequency combined with the same 1.7%
delta gives him a higher relative ratio (rr = 1.15). The most striking
champions by rate ratio rather than raw delta are Sona (rr = 2.45) and
Tryndamere (rr = 2.15), both are niche picks with very low overall
support, but when they appear in winning teams the proportional
difference is large. Rek’Sai (rr = 1.77) and Kayle (rr = 1.86) also
stand out as low-frequency but high win-rate picks, consistent with
their presence in the CAR rules. This pattern suggests that the most
predictive champions in Season 11 were not the most popular ones but
rather specialist picks that appeared infrequently but at a
disproportionately high rate on winning teams.
# MBA
rules_df <- DATAFRAME(head(rules_sorted, 20))
mba_champs <- unique(c(
gsub("\\{|\\}", "", as.character(rules_df$LHS)),
gsub("\\{|\\}", "", as.character(rules_df$RHS))
))
mba_champs <- trimws(mba_champs[mba_champs != ""])
# CAR - split multi-champion LHS into individual names
car_df <- DATAFRAME(rules_victory_sorted)
car_raw <- gsub("\\{|\\}", "", as.character(car_df$LHS))
car_champs <- unique(trimws(unlist(strsplit(car_raw, ","))))
car_champs <- car_champs[!grepl("CLASS", car_champs) & car_champs != ""]
# delta top 10
delta_champs <- champ_by_class %>%
slice_head(n = 10) %>%
pull(champion)
# intersections
all_three <- intersect(intersect(mba_champs, car_champs), delta_champs)
two_mba_car <- setdiff(intersect(mba_champs, car_champs), delta_champs)
two_mba_delta <- setdiff(intersect(mba_champs, delta_champs), car_champs)
two_car_delta <- setdiff(intersect(car_champs, delta_champs), mba_champs)
cat("Champions in ALL three methods:\n")
## Champions in ALL three methods:
cat(paste(sort(all_three), collapse = ", "), "\n\n")
## Alistar, Jhin, Kai'Sa, Lucian, Nidalee, Sett
cat("MBA + CAR only:\n")
## MBA + CAR only:
cat(paste(sort(two_mba_car), collapse = ", "), "\n\n")
## Akali, Graves, Malphite, Viktor
cat("MBA + Delta only:\n")
## MBA + Delta only:
cat(paste(sort(two_mba_delta), collapse = ", "), "\n\n")
## Gragas
cat("CAR + Delta only:\n")
## CAR + Delta only:
cat(paste(sort(two_car_delta), collapse = ", "), "\n\n")
## Hecarim, Irelia, Rek'Sai
# summary table
validation_df <- data.frame(
Champion = c(all_three, two_mba_car, two_mba_delta, two_car_delta),
MBA = c(rep("✓", length(all_three)), rep("✓", length(two_mba_car)),
rep("✓", length(two_mba_delta)), rep("✗", length(two_car_delta))),
CAR = c(rep("✓", length(all_three)), rep("✓", length(two_mba_car)),
rep("✗", length(two_mba_delta)), rep("✓", length(two_car_delta))),
Delta = c(rep("✓", length(all_three)), rep("✗", length(two_mba_car)),
rep("✓", length(two_mba_delta)), rep("✓", length(two_car_delta))),
Strength = c(rep("All three", length(all_three)),
rep("MBA + CAR", length(two_mba_car)),
rep("MBA + Delta", length(two_mba_delta)),
rep("CAR + Delta", length(two_car_delta)))
)
knitr::kable(validation_df,
caption = "Cross-method validation of champion significance")
| Champion | MBA | CAR | Delta | Strength |
|---|---|---|---|---|
| Nidalee | ✓ | ✓ | ✓ | All three |
| Sett | ✓ | ✓ | ✓ | All three |
| Alistar | ✓ | ✓ | ✓ | All three |
| Lucian | ✓ | ✓ | ✓ | All three |
| Kai’Sa | ✓ | ✓ | ✓ | All three |
| Jhin | ✓ | ✓ | ✓ | All three |
| Viktor | ✓ | ✓ | ✗ | MBA + CAR |
| Malphite | ✓ | ✓ | ✗ | MBA + CAR |
| Graves | ✓ | ✓ | ✗ | MBA + CAR |
| Akali | ✓ | ✓ | ✗ | MBA + CAR |
| Gragas | ✓ | ✗ | ✓ | MBA + Delta |
| Hecarim | ✗ | ✓ | ✓ | CAR + Delta |
| Rek’Sai | ✗ | ✓ | ✓ | CAR + Delta |
| Irelia | ✗ | ✓ | ✓ | CAR + Delta |
The cross-method validation table reveals a clear hierarchy of evidence among Season 11 High Elo champions.
Six champions - Alistar, Jhin, Kai’Sa, Lucian, Nidalee, and Sett, are validated by all three independent analytical methods simultaneously. This convergence is the strongest possible signal in this analysis: these champions were not only synergistic with specific partners in winning compositions (MBA), but also appeared in high-confidence victory-predicting combinations (CAR), and showed a statistically higher presence on winning teams compared to losing teams (delta support). They represent the core of the Season 11 High Elo meta.
The second tier, Akali, Graves, Malphite, and Viktor, appears in both MBA synergy rules and CAR victory rules but falls outside the delta support top 10. This suggests their value is composition-dependent: they are strong when paired with the right champions, but their individual win-rate advantage over the full dataset is less pronounced.
Gragas shows the opposite pattern, appearing in MBA and delta support but not in CAR. This is consistent with his role as a flexible jungle pick that pairs well with many different compositions but rarely anchors a single high-confidence winning combination on his own.
Finally, Hecarim, Irelia, and Rek’Sai are validated by CAR and delta support but are absent from the top MBA pairwise synergies. These are picks whose value appears to be more individual than combo-dependent, they contribute to winning outcomes regardless of who they are paired with, rather than through a specific partner synergy.
This analysis applied three complementary Market Basket Analysis techniques to 2,901 winning team compositions from Season 11 High Elo ranked play, and the results converge on a consistent and interpretable picture of how successful teams were built during that period.
The Apriori rules identify the Aphelios–Thresh bot lane as the single strongest statistical synergy in the dataset (lift = 4.36), a pairing whose mechanical interdependence — an immobile hypercarry paired with a peeling, engaging support, translates directly into co-occurrence patterns in winning teams. The Yone–Taliyah and Renekton–Taliyah combinations form a second tier of intentional synergies built around Taliyah as a mid-lane playmaker, reflecting a Season 11 meta trend toward coordinated, map-controlling compositions.
The pair synergy network adds structural context: Kai’Sa and Graves emerge as the most central champions in the winner graph, functioning as flexible picks that connected across multiple team archetypes rather than being tied to a single composition style. Their high degree centrality suggests they were “glue” champions, picks that worked well regardless of what the rest of the team was doing.
The CAR and delta support analyses together reveal an important distinction between popular and predictive champions. High-pick-rate champions like Jhin and Kai’Sa appear frequently in both winning and losing teams, making their raw frequency uninformative about outcome. The truly win-predictive picks in Season 11 were a mix of popular flexible carries and lower-frequency specialists - Sona (rr = 2.45), Tryndamere (rr = 2.15), Kayle (rr = 1.86), and Rek’Sai (rr = 1.77) appeared rarely but at a disproportionately high rate on the winning side, suggesting that correctly identifying and executing niche picks may have been as valuable as mastering the most popular meta champions.
The cross-method validation synthesizes all three perspectives into a single hierarchy of evidence. Six champions - Alistar, Jhin, Kai’Sa, Lucian, Nidalee, and Sett - are confirmed by all three methods simultaneously, representing the most robustly supported picks of the season. A secondary tier of composition-dependent picks (Akali, Graves, Malphite, Viktor) and individually strong picks (Hecarim, Irelia, Rek’Sai) completes the picture of how different champions contributed to winning in different ways.
As with all Market Basket Analysis, these findings are descriptive and should be interpreted as correlates rather than causes of winning. No patch or meta-shift control was applied, roles and lane assignments were not modeled, and the dataset reflects a single season snapshot. Future work could extend this analysis by incorporating patch version as a temporal variable, modeling lane-specific synergies to distinguish bot lane duos from cross-lane compositions, or applying the same framework to other seasons to track how the meta evolved over time.
sessionInfo()
## R version 4.5.2 (2025-10-31)
## Platform: aarch64-apple-darwin20
## Running under: macOS Sequoia 15.5
##
## Matrix products: default
## BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: Europe/Warsaw
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] arulesViz_1.5.4 arules_1.7.13 Matrix_1.7-4 viridis_0.6.5
## [5] viridisLite_0.4.2 tidygraph_1.3.1 ggraph_2.2.2 ggplot2_4.0.1
## [9] knitr_1.51 stringr_1.6.0 tidyr_1.3.2 dplyr_1.1.4
## [13] readr_2.1.6
##
## loaded via a namespace (and not attached):
## [1] gtable_0.3.6 xfun_0.55 bslib_0.9.0 htmlwidgets_1.6.4
## [5] visNetwork_2.1.4 ggrepel_0.9.6 lattice_0.22-7 tzdb_0.5.0
## [9] vctrs_0.6.5 tools_4.5.2 generics_0.1.4 parallel_4.5.2
## [13] ca_0.71.1 tibble_3.3.0 pkgconfig_2.0.3 RColorBrewer_1.1-3
## [17] S7_0.2.1 lifecycle_1.0.4 compiler_4.5.2 farver_2.1.2
## [21] ggforce_0.5.0 codetools_0.2-20 graphlayouts_1.2.3 seriation_1.5.8
## [25] htmltools_0.5.9 sass_0.4.10 yaml_2.3.12 pillar_1.11.1
## [29] crayon_1.5.3 jquerylib_0.1.4 MASS_7.3-65 cachem_1.1.0
## [33] iterators_1.0.14 foreach_1.5.2 TSP_1.2.6 tidyselect_1.2.1
## [37] digest_0.6.39 stringi_1.8.7 purrr_1.2.1 forcats_1.0.1
## [41] labeling_0.4.3 polyclip_1.10-7 fastmap_1.2.0 grid_4.5.2
## [45] cli_3.6.5 magrittr_2.0.4 withr_3.0.2 scales_1.4.0
## [49] bit64_4.6.0-1 registry_0.5-1 rmarkdown_2.30 igraph_2.2.2
## [53] bit_4.6.0 otel_0.2.0 gridExtra_2.3 hms_1.1.4
## [57] memoise_2.0.1 evaluate_1.0.5 rlang_1.1.6 Rcpp_1.1.0
## [61] glue_1.8.0 tweenr_2.0.3 rstudioapi_0.17.1 vroom_1.6.7
## [65] jsonlite_2.0.0 R6_2.6.1