Skip to content

Instantly share code, notes, and snippets.

@bbatliner
Created October 9, 2016 21:50
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bbatliner/a43ed6dcb01fd16d7e78c8a19f92aef8 to your computer and use it in GitHub Desktop.
Save bbatliner/a43ed6dcb01fd16d7e78c8a19f92aef8 to your computer and use it in GitHub Desktop.

League of Stats

Motivating question

How can we quantify an individual's influence on the outcome of a single game of League of Legends? Which metrics (indivdual, team, or enemy metrics) are the strongest predictors of game outcome?

Methods

For any individual, we determine or calculate the following metrics:

  • Time
  • Kills (K)
  • Deaths (D)
  • Assists (A)
  • KDA Ratio
  • Wards Placed
  • Wards Per Minute (WPM)
  • Team K
  • Team D
  • Team A
  • Team KDA
  • Enemy K
  • Enemy D
  • Enemy A
  • Enemy KDA
  • (Team A - Enemy A)
  • (Team KDA - Enemy KDA)
  • Team Wards Placed
  • Enemy Wards Placed
  • (Team Wards - Enemy Wards)
  • Kill Participation (KP)
  • Death Percentage (DP)
  • Ward Percentage
  • (Team KDA - KDA)
  • W/L

The raw data can be scraped from op.gg. See Scraping for more details.

We are interested to see which of these metrics is the strongest predictor of game outcome. To begin, we create a plot of game outcome versus each variable. Example:

Outcome vs KP example plot

For any of these plots, we have the option of running a logistic regression (we used this online tool) in order to calculate the probability of a win for various values of the metric. Example:

Probability of win vs individual KDA logistic regression

We analysed the following games in our research:

  • C9 Sneaky's 40 most recent ranked ADC games
  • SoerenBjerg's (Bjergsen) 21 most recent ranked mid games
  • C9 Bunny FuFuu's 24 most recent ranked support games
  • Beester's (that's me!) 40 most recent solo ranked support games
  • skyfall3665's (that's my friend!) 40 most recent ranked mid games

Excel spreadsheets containing our analyses can be viewed/downloaded here:

Findings

We discovered the following trends:

  1. The two strongest predictors of game outcome are team KDA and individual KDA.
  2. Warding metrics have no influence on the outcome of the game until Challenger.

Team KDA and Individual KDA

No other metrics come close to the strength of these in predicting the outcome of a game. If your team does well, you will win.

Beester's Probability of Win vs Team KDA logistic regression

What's interesting, however, is that Team KDA plays a smaller role in determining game outcome at lower elos. Consider the following graphs which show the trend:

Silver 4 Outcome vs Team KDA Diamond 5 Outcome vs Team KDA Challenger Outcome vs Team KDA

Even without logistic regressions, it can be seen that Team KDA is more predictive of game outcome at high elos, presumably because teams can translate kill and gold advantages into objectives.

The complementary trend holds, too. That is, individual KDA plays a larger role in lower elos.

Warding

Surprisingly, wards placed, wards per minute, and wards placed differentials between teams had no strength in predicting game outcomes, except Challenger games. The conclusions are demonstrated in the following graphs:

Beester's probability of win vs team wards - enemy wards logistic regression Bjergsen's probability of win vs team wards - enemy wards logistic regression

The ward differentials of +/- 15 wards in Diamond 5 had no strength in predicting game outcome, yet those same differentials had a high strength in Challenger. We can conclude that teams are unable to make the best use of vision advantages until the highest of elos.

Summary

In solo queue, the most influence you can have on a game's outcome is with your KDA. That is, to climb the ladder, it is first and foremost necessary to be able to win fights (micro play) and/or create advantageous situations for your team (macro play). However, and especially in higher elos, the combined efforts of your team are more influential than any one individual's play.

Additionally, warding in solo queue is probably overrated. If you're not Challenger, then the players on your team aren't going to be able to make the most of any vision you provide, or opportunities that arise because of fights for vision. Simply keeping up in wards placed and getting vision somewhere on the map is good enough.

Most importantly, recognize that as an individual you cannot completely determine the outcome of a game. In other words, solo carrying is not a thing. If you are committed to climbing the ranked ladder, prepare for the long haul, and focus on consistent performance game after game, rather than trying to carry all of your teammates, who are more in control of your destiny than you are.

Next steps

The biggest weakness of these analyses is how few players are included. The likelihood of any one player in our analyses being an outlier is high. Studying more players and games would help us draw conclusions about the state of role, such as support or mid, more accurately than with just one or two players sampled.

Other stats to consider analyzing:

  • Damage percentage
  • CS per minute
  • Vision wards

About me

I'm Beester, a Diamond 5 support main. I was getting frustrated with the seeming lack of influence I have in my games and wanted to quantify it with data! This was my first foray into League analytics and I had fun learning about logistic regression and scraping data from webpages.

Scraping data from op.gg

These scripts were made on na.op.gg and might not work with other regions. If op.gg changes the structure of their pages, these scripts might not work.

To use, "expand"/"extend" each game you wish to collect data from. Then, copy and paste each of these to your JavaScript console (F12), and copy the results into Excel. It is important to keep your data unsorted in Excel so that the data returned from these scripts is consistent.

Win/Lose:

Array.from(document.querySelectorAll('.GameItem.extended')).map(node => node.classList.contains('Win') ? 1 : 0).join('\r\n')

Champion names:

Array.from(document.querySelectorAll('.GameItem.extended .ChampionName')).map(node => node.querySelector('a').textContent).join('\r\n')

Game time:

Array.from(document.querySelectorAll('.GameItem.extended .GameLength')).map(node => node.innerHTML).map(time => '00:' + time.split(' ').map(num => parseInt(num)).join(':')).join('\r\n')

Kills:

Array.from(document.querySelectorAll('.GameItem.extended .Row.isRequester .KDA .Kill')).map(node => parseInt(node.textContent)).join('\r\n')

Deaths:

Array.from(document.querySelectorAll('.GameItem.extended .Row.isRequester .KDA .Death')).map(node => parseInt(node.textContent)).join('\r\n')

Assists:

Array.from(document.querySelectorAll('.GameItem.extended .Row.isRequester .KDA .Assist')).map(node => parseInt(node.textContent)).join('\r\n')

Team Kills:

Array.from(document.querySelectorAll('.GameItem.extended .Summary > :first-child .Kill')).map(node => parseInt(node.textContent)).join('\r\n')

Team Deaths:

Array.from(document.querySelectorAll('.GameItem.extended .Summary > :first-child .Death')).map(node => parseInt(node.textContent)).join('\r\n')

Team Assists:

Array.from(document.querySelectorAll('.GameItem.extended .Summary > :first-child .Assist')).map(node => parseInt(node.textContent)).join('\r\n')

Enemy Kills:

Array.from(document.querySelectorAll('.GameItem.extended .Summary > :last-child .Kill')).map(node => parseInt(node.textContent)).join('\r\n')

Enemy Deaths:

Array.from(document.querySelectorAll('.GameItem.extended .Summary > :last-child .Death')).map(node => parseInt(node.textContent)).join('\r\n')

Enemy Assists:

Array.from(document.querySelectorAll('.GameItem.extended .Summary > :last-child .Assist')).map(node => parseInt(node.textContent)).join('\r\n')

Wards placed:

Array.from(document.querySelectorAll('.GameItem.extended .Row.isRequester .Stats')).map(node => parseInt(node.querySelector('span').textContent)).join('\r\n')

Team wards placed:

Array.from(document.querySelectorAll('.GameDetailTable:first-child .Row .Stats > :first-child')).chunk_inefficient(5).map(arr => arr.map(node => parseInt(node.innerHTML)).sum()).join('\r\n')

Enemy wards placed:

Array.from(document.querySelectorAll('.GameDetailTable:last-child .Row .Stats > :first-child')).chunk_inefficient(5).map(arr => arr.map(node => parseInt(node.innerHTML)).sum()).join('\r\n')

Utilty functions (for ward calculations):

Object.defineProperty(Array.prototype, 'chunk_inefficient', {
    value: function(chunkSize) {
        var array=this;
        return [].concat.apply([],
            array.map(function(elem,i) {
                return i%chunkSize ? [] : [array.slice(i,i+chunkSize)];
            })
        );
    }
});
Object.defineProperty(Array.prototype, 'sum', {
    value: function(chunkSize) {
        var sum = 0;
        for (var i = 0; i < this.length; i++) {
            sum += this[i];
        }
        return sum;
    }
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment