Hello! I share with you this personal journey, as a fan of a great League of Legends (LoL) player called IM ONI, and how through the data that the game offers I have slowly managed to advance in my professional reconversion towards Data Science and Data Engineering. In this post, I will take you through my experience and highlight how I used my skills to analyze and visualize data from the world of LoL.
Like many, I became a big fan of IM ONI because of their expertise in the world of LoL. However, my interest was not limited to just watching his games; I also wanted to understand the data behind his gameplay and discover patterns and trends that could explain his gameplay. This desire to explore and analyze data led me to embark on a journey that connects the extraction, transformation and loading – ETL – of LOL data, visualization and a first analysis.
League of Legends (LoL): is a popular online video game of the MOBA (Multiplayer Online Battle Arena) genre developed and published by Riot Games. In LoL, players control a character, known as a “champion”, with unique abilities, and work as a team to destroy the enemy base while defending their own. The game is notable for its focus on strategy, team cooperation and individual skill.
According to the Active Player at the time of writing 1,050,863 people are playing online, in the last 30 days more than 133,466,282 players have connected and more than 97,975,355 hours are watched on Twitch with a peak of 712,950 simultaneous viewers.
It is important to highlight the help of the content of two Youtubers CodinEric y Jack J de iTero Gaming who have served me as a reference with their videos and explanations. It was not only a matter of following their code, but to understand the process and programming logic they use for data processing.
Configuring the development environment
My first step was to set up my development environment, installing the necessary Python libraries and obtaining an API key from Riot Games to access the LoL APIs. This initial setup was crucial to be able to access the game data and begin my analysis.
Development environment: I used JupyterLab to write the Notebook with Python locally. I always use Conda to generate an environment and install the libraries according to the requirements.
# Libraries
import requests
from time import sleep
import pandas as pd
from datetime import datetime
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.graph_objs as go
import plotly.express as px
Riot Games API Key: Go to https://developer.riotgames.com/, then create or log in with your Riot account and accept the terms of service. At the bottom, click “I’m not a robot” and regenerate your API key. For Riot Games security policies, this key is deactivated every 24 hours or less according to their instructions, then you will have to repeat the procedure as many times as necessary during the project. For practical purposes, I will make the “api_key” visible on the notebook, but I make it clear that this is a bad practice.
Then copy the API key and paste it in your notebook under the “api_key” variable, example:
api_key = 'RGAPI-0f2ead54-465c-2323-8bd6-c123456789'
It is very important to understand that you MUST NOT share your passwords in public, normally you can use environment variables that wrap your password and at the moment of publishing your code they are not visible (I understood it as a GitIgnore, to not upload or make visible what you don't want).
# Insert an environment variable:
"""
1 - Create an .env file in the root of the project.
2 - $ export api_key = RGAPI-1234567-1234567890
3 - $ source -env
"""
# api_key = os.environ['api_key'] => To call the environment variable.
Utility of Consuming the League of Legends API
Consuming the League of Legends API provides access to a wealth of data about the game, players and games. This data can be used for a variety of purposes, including:
- Player Performance Analysis: Game data can be used to analyze player performance, identify areas for improvement and compare statistics with other players.
- Strategy and Trend Study: Analyzing game data can identify trends in champion selection, game strategies and game results, which can help players improve their strategy and adapt to the current meta.
- Data Visualization: Game data can be visualized in a variety of ways, such as interactive graphs and charts, to better understand game dynamics, share findings, and perform detailed reporting.
- Academic Research: League of Legends data can also be used for academic purposes, such as player behavior studies, social network analysis, and complex systems modeling.
In short, consuming the League of Legends API provides a valuable source of data for game analysis and understanding, as well as for improving individual and collective player performance.
You can access all the documentation for the LOL API consumption mode at https://developer.riotgames.com/docs/lol.
Player data query
Using the LoL APIs, I was able to get detailed information about players, including their summoner name, level, game history and more. This allowed me to study the performance of different players, including IM ONI.
In the beginning, we only know the name of the player “summoner” and the region (server) where he plays, for our case: IM ONI and euw (server in Europe).
Until November 2023 you could use the API “Summoner-V4” in its endpoint “/lol/summoner/v4/summoners/by-name/{summonerName}” to get the player’s ID. Riot Games uses three (3) different Ids to track it: The PUUID, the SummonerId and the AccountId. Actually, the first two are in effect and are needed to make queries according to the endpoint (API location where a system interacts with a web API.).
Currently, Riot Games suggests using the “Account-v1” API to obtain the PUUID (Player Universal Unique Identifiers), which is a string type (up to 78 characters) to track the player worldwide, even if he/she changes region (server).
To get it go to the endpoint “/riot/account/v1/accounts/by-riot-id/{gameName}/{tagLine}” in the API “Account-V1” that you can find when you login with your account at developer.riotgames.com/apis. In the “Path parameters” section insert in “tagLine” = euw1 (player’s region) and in “gameName” = IIM ONI (player’s name), select EUROPE (or player’s region) and run the query. This will generate a URL with the returned data.
You copy the URL it generates and paste it into your notebook as a new variable “api_url”.
api_url = https://europe.api.riotgames.com/riot/account/v1/accounts/by-riot-id/IIM%20ONI/EUW?api_key=RGAPI-0f2ead89-905c-4393-8bd6-c123456789"
Then, with the library “requests” already installed we validate the connection like this:
requests.get(api_url)
<Response [200]>
You should get a response [200] as a successful connection. If you have a connection error response like “400, 401, etc.” validate in the response error list of each endpoint. (Usually you will get an error because the api key expired and you will have to create a new one).
Then you can access the information in Json format and save it in a variable.
resp = requests.get(api_url)
player_info = resp.json()
player_info
$ {'puuid': '3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ',
'gameName': 'IIM ONI',
'tagLine': 'EUW'}
Now with the PUUID, we get the SUMMONER ID with the endpoint “/lol/summoner/v4/summoners/by-puuid/{encryptedPUUID}“.
We do the same procedure on the “Summoner-V4” API and get a new URL:
api_url = 'https://euw1.api.riotgames.com/lol/summoner/v4/summoners/by-puuid/3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ?api_key=RGAPI-0f2ead12-123c-1234-1bd2-c0123456789'
resp = requests.get(api_url)
summoner = resp.json()
summoner
$ {'id': 'GKLboy4jZh2vTZDsax2s9lX3hfceyCbHzAdc0j5nPkpbgy4F',
'accountId': 'w6J4cvKF2LD5XyIDo9kxTAEymbFe0dil8UwtJ3EaMs_ylwrloap3YO62',
'puuid': '3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ',
'profileIconId': 6503,
'revisionDate': 1713898945000,
'summonerLevel': 329}
With the “Puuid” and the “SummonerId” we will be able to make queries to other APIS such as “League-V4” or “Champion-mastery“.
Game Data Collection
Exploring recent game data was especially exciting. I was able to access detailed information about the games, such as selected champions, game results, and game statistics. This allowed me to identify common trends and strategies among the players in the games. Recall that LOL is a five-on-five game, but a player can compete alone and level up.
The first thing they ask you when you play LOL is what is your ranking level, so to find your player’s ranking let’s query the “League-v4” API in the endpoint “/lol/league/v4/entries/by-summoner/{encryptedSummonerId}” this information will be the Mastery Ranking.
summ_id = summoner['id']
endpoint = f'https://euw1.api.riotgames.com/lol/league/v4/entries/by-summoner/{summ_id}?api_key={api_key}'
res = requests.get(endpoint).json()
df_rank = pd.DataFrame([res[0]]) # create dataframe with Ranking Mastery
df_rank
Now it is the turn of the match data, for that we will use the API “Match-v5” with the PUUID that we obtained.
api_url = "https://europe.api.riotgames.com/lol/match/v5/matches/by-puuid/3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ/ids?start=0&count=20&api_key=RGAPI-0f2ead89-905c-4393-8bd6-12345678"
# First we bring the Ids for the last 20 games
resp = requests.get(api_url)
match_ids = resp.json()
match_ids
['EUW1_6832340332',
'EUW1_6832299147',
'EUW1_6829175370',
'EUW1_6829148496',
'EUW1_6829025866',
'EUW1_6828945398',
'EUW1_6828887595',
'EUW1_6828867611',
'EUW1_6828812533',
'EUW1_6824214120',
'EUW1_6824173176',
'EUW1_6824124368',
'EUW1_6823607057',
'EUW1_6823569193',
'EUW1_6816330441',
'EUW1_6816300579',
'EUW1_6814475777',
'EUW1_6814449666',
'EUW1_6811931211',
'EUW1_6811890150']
We filter the most recent match:
recent_match = match_ids[0]
recent_match
'EUW1_6832340332'
We use the API “/lol/match/v5/matches/{matchId}” to extract the game data with its id.
api_url = "https://europe.api.riotgames.com/lol/match/v5/matches/EUW1_6832340332?api_key=RGAPI-0f2ead89-905c-4393-8bd6-1234567"
resp = requests.get(api_url)
match_data = resp.json()
match_data
This returns a Json file of 2823 lines of code. Here are the first 20:
{'metadata': {'dataVersion': '2',
'matchId': 'EUW1_6832340332',
'participants': ['q0gtmms-y3amkJoRws5bFeAmP6a9XmfuwXFOVshyby8dNHkiGlFq0ao-Uxe21RfBKosrEbiR7CT-5w',
'pSH5lRUAcE0Cqk-bKTlH0IYFZa8pfpXT6rQf8ybaRmnSJv0LNiCaXUeYqy6wJ36duvYDIl6u8lmzaA',
'3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ',
'81pnwBf13NcO4q0k2EdH-nE3NsjJnlrYXdiR1xnKmff2qs_cLz-D540Q-RjgrSa-dA4sbmsAq-JTjQ',
'XjEV9TRmLaQoRx4tBl99oztaP8rCOJxCTghiJBeoqGGi0Wcv7O2c-utaW_-t797QFW-xZg1iRMSamg',
'23prBqAyWrlkEK89A29ACLZ9pDKNxcIe5cR4SOdhmyyvtxwsU0O0k2SUnx2xzPcWgqh1IK8ltpk1qQ',
'UteRJTU3g1NP_67ENlqRpX8QhTO8OooBHF4S32bvEFutfaMeQQipmfmi1ExMTJCHos4glinM7ZJEWw',
'G-Lj_m6A3K4pvid9h6b-M9h17D5_-uhPA6HD4ujt78EIq1bx4Dga-Tyb4RujvvGk5cdZtvygp96vFA',
'M0j0ja9BC0OjyUg-MCF3YpdU3U-_SMpvTUq7Kxzh9GSvwil3RiBs6O50qGARFYnoiyr4zzsXtpsKIg',
'G5R1mdsETPnJNKub3tmpI7LqWchEp2dOao_coGZerFMjybtRwKk7ShHposUGWMk53y_F1b0WjCbPjA']},
'info': {'endOfGameResult': 'GameComplete',
'gameCreation': 1708906756855,
'gameDuration': 1829,
'gameEndTimestamp': 1708908667752,
'gameId': 6832340332,
'gameMode': 'CLASSIC',
'gameName': 'teambuilder-match-6832340332',
'gameStartTimestamp': 1708906838802,
...
# Match_data has two sections
match_data.keys()
dict_keys(['metadata', 'info'])
# "Metadata" contains the puuid of each player in list 'participants'.
match_data['metadata'] # This returns the PUUID of all 10 players in the game.
{'dataVersion': '2',
'matchId': 'EUW1_6832340332',
'participants': ['q0gtmms-y3amkJoRws5bFeAmP6a9XmfuwXFOVshyby8dNHkiGlFq0ao-Uxe21RfBKosrEbiR7CT-5w',
'pSH5lRUAcE0Cqk-bKTlH0IYFZa8pfpXT6rQf8ybaRmnSJv0LNiCaXUeYqy6wJ36duvYDIl6u8lmzaA',
'3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ',
'81pnwBf13NcO4q0k2EdH-nE3NsjJnlrYXdiR1xnKmff2qs_cLz-D540Q-RjgrSa-dA4sbmsAq-JTjQ',
'XjEV9TRmLaQoRx4tBl99oztaP8rCOJxCTghiJBeoqGGi0Wcv7O2c-utaW_-t797QFW-xZg1iRMSamg',
'23prBqAyWrlkEK89A29ACLZ9pDKNxcIe5cR4SOdhmyyvtxwsU0O0k2SUnx2xzPcWgqh1IK8ltpk1qQ',
'UteRJTU3g1NP_67ENlqRpX8QhTO8OooBHF4S32bvEFutfaMeQQipmfmi1ExMTJCHos4glinM7ZJEWw',
'G-Lj_m6A3K4pvid9h6b-M9h17D5_-uhPA6HD4ujt78EIq1bx4Dga-Tyb4RujvvGk5cdZtvygp96vFA',
'M0j0ja9BC0OjyUg-MCF3YpdU3U-_SMpvTUq7Kxzh9GSvwil3RiBs6O50qGARFYnoiyr4zzsXtpsKIg',
'G5R1mdsETPnJNKub3tmpI7LqWchEp2dOao_coGZerFMjybtRwKk7ShHposUGWMk53y_F1b0WjCbPjA']}
# info" contains a lot of information about the game, such as when it was created, how long it lasted, etc.
match_data['info'].keys()
dict_keys(['endOfGameResult', 'gameCreation', 'gameDuration', 'gameEndTimestamp', 'gameId', 'gameMode', 'gameName', 'gameStartTimestamp', 'gameType', 'gameVersion', 'mapId', 'participants', 'platformId', 'queueId', 'teams', 'tournamentCode'])
# We can see the duration of the game with "gameDuration":
match_data['info']['gameDuration'] / 60 # / 60 to get it into minutes.
30.483333333333334 # Minutes duration.
# Inside info, participants contains a list of length 10, each with more information about the player
len(match_data['info']['participants'])
10 # participants
Obtaining data from Im ONI:
We take the puuid list of all participants and use “index” to find out where our player Im ONI is.
# A list of all the participants puuids
participants = match_data['metadata']['participants']
# Now, find where in the data our players puuid is found
player_index = participants.index(puuid)
player_index
2
This should match the puuid we use to search for matching IDs.
participants[player_index]
'3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ'
Let’s check that it works by using this index to get the name of the players from the data.
# We verify the Summoner name we used in the first API ACCOUNT-V1
match_data['info']['participants'][player_index]['summonerName']
'IIM ONI'
# Now that we know where our player is, we can get his data
player_data = match_data['info']['info']['participants'][player_index]
player_data['championName'] # Which Champion is playing with
'Hwei'
# We get his KDA
k = player_data['kills']
d = player_data['deaths']
a = player_data['assists']
win = player_data['win']
print("Kills:", k)
print("Deaths:", d)
print("Assists:", a)
print("KDA:", (k + a) / d)
print("Win:", win)
Kills: 10
Deaths: 10
Assists: 8
KDA: 1.8
Win: False
# Let's get your role or position
player_data['teamPosition']]
'MIDDLE'
Champion Data Exploration
Another interesting aspect was exploring data on the champions available in LoL. I consulted information on base stats, skills and more, which helped me better understand the strengths and weaknesses of each champion and their impact on the game.
We used the Champion Mastery API “/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}” which returns the following endpoint:
endpoint = “https://euw1.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-puuid/3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ?api_key=RGAPI-6ee36def-3911-48e7-84e8-1234567890”
res = requests.get(endpoint).json()
df_champs = pd.DataFrame(res)
df_champs
# We change the type of date with the Pandas datatime method and export the CSV with the production date.
df_champs['lastPlayTime'] = pd.to_datetime(df_champs['lastPlayTime'], unit = 'ms')
df_champs.to_csv(f'champs_mastery_{datetime.today().strftime("%Y_%m_%d")}.csv', index = False)
We will now be able to further analyze this dataframe with time series in Pandas.
Data collection
Learning how to connect to the APIs, do the queries and get the data is a process that I still struggle to understand at several points. In the end, our goal is to collect as much data from our IM ONI player, and although the learning curve is steep, the fastest and most orderly way is to create functions. To understand the structure and usefulness of each Function I used the mentioned Youtube channels, reference websites about LOL content, the official documentation of the game and some exercises done within the learning path in Data Engineering with Platzi.
Function #1 – Obtain a player’s PUUID
The first function simply obtains the puuid, given an invoker name and a region.
def get_puuid(summoner_name, region, api_key, mass_region):
api_url = (
"https://" +
mass_region +
".api.riotgames.com/riot/account/v1/accounts/by-riot-id/" +
summoner_name +"/"+
region +
"?api_key=" +
api_key
)
print(api_url)
resp = requests.get(api_url)
puuid = player_info['puuid']
return puuid
summoner_name = 'IIM%20ONI'
mass_region = 'europe'
region = 'euw1'
puuid_oni = get_puuid(summoner_name, region, api_key, mass_region)
puuid_oni
https://europe.api.riotgames.com/riot/account/v1/accounts/by-riot-id/IIM%20ONI/euw1?api_key=RGAPI-0f2ead89-123c-1234-1bd2-c123456789
'3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ'
What we did was to build the API url, do the query and get the PUUID. It’s the same as our first step, only now it’s modularized and optimized.
Function #2 – List of Ids of the last 20 games
The function to obtain a list of all match IDs (2nd example above) given a player puuid and the mass_region.
def get_match_ids(puuid, mass_region, api_key):
api_url = (
"https://" +
mass_region +
".api.riotgames.com/lol/match/v5/matches/by-puuid/" +
puuid +
"/ids?start=0&count=20" +
"&api_key=" +
api_key
)
print(api_url)
resp = requests.get(api_url)
match_ids = resp.json()
return match_ids
# NOTE: region and mass_region are different.
# for example, NA1 is the region of North America
# which is part of the mass region AMERICAS
# EUW1 is the Western Europe region, which is part of the mass region EUROPE
mass_region = 'EUROPE
match_ids = get_match_ids(puuid, mass_region, api_key)
match_ids
https://EUROPE.api.riotgames.com/lol/match/v5/matches/by-puuid/3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ/ids?start=0&count=20&api_key=RGAPI-0f2ead89-905c-4393-8bd6-c0112c330526
['EUW1_6832340332', 'EUW1_6832299147', 'EUW1_6829175370', 'EUW1_6829148496', 'EUW1_6829025866', 'EUW1_6828945398', 'EUW1_6828887595', 'EUW1_6828867611', 'EUW1_6828812533', 'EUW1_6824214120', 'EUW1_6824173176', 'EUW1_6824124368', 'EUW1_6823607057', 'EUW1_6823569193', 'EUW1_6816330441', 'EUW1_6816300579', 'EUW1_6814475777', 'EUW1_6814449666', 'EUW1_6811931211', 'EUW1_6811890150']
Function # 3 – Set data
From a given match ID and mass region, obtain data on the match.
def get_match_data(match_id, mass_region, api_key):
api_url = (
“https://” +
mass_region +
“.api.riotgames.com/lol/match/v5/matches/” +
match_id +
“?api_key=” +
api_key
)
resp = requests.get(api_url)
match_data = resp.json()
return match_data
match_id = match_ids[0]
match_data = get_match_data(match_id, mass_region, api_key)
match_data
# Recall that it returns 2823 lines of code, here are the first 20.
{'metadata': {'dataVersion': '2',
'matchId': 'EUW1_6832340332',
'participants': ['q0gtmms-y3amkJoRws5bFeAmP6a9XmfuwXFOVshyby8dNHkiGlFq0ao-Uxe21RfBKosrEbiR7CT-5w',
'pSH5lRUAcE0Cqk-bKTlH0IYFZa8pfpXT6rQf8ybaRmnSJv0LNiCaXUeYqy6wJ36duvYDIl6u8lmzaA',
'3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ',
'81pnwBf13NcO4q0k2EdH-nE3NsjJnlrYXdiR1xnKmff2qs_cLz-D540Q-RjgrSa-dA4sbmsAq-JTjQ',
'XjEV9TRmLaQoRx4tBl99oztaP8rCOJxCTghiJBeoqGGi0Wcv7O2c-utaW_-t797QFW-xZg1iRMSamg',
'23prBqAyWrlkEK89A29ACLZ9pDKNxcIe5cR4SOdhmyyvtxwsU0O0k2SUnx2xzPcWgqh1IK8ltpk1qQ',
'UteRJTU3g1NP_67ENlqRpX8QhTO8OooBHF4S32bvEFutfaMeQQipmfmi1ExMTJCHos4glinM7ZJEWw',
'G-Lj_m6A3K4pvid9h6b-M9h17D5_-uhPA6HD4ujt78EIq1bx4Dga-Tyb4RujvvGk5cdZtvygp96vFA',
'M0j0ja9BC0OjyUg-MCF3YpdU3U-_SMpvTUq7Kxzh9GSvwil3RiBs6O50qGARFYnoiyr4zzsXtpsKIg',
'G5R1mdsETPnJNKub3tmpI7LqWchEp2dOao_coGZerFMjybtRwKk7ShHposUGWMk53y_F1b0WjCbPjA']},
'info': {'endOfGameResult': 'GameComplete',
'gameCreation': 1708906756855,
'gameDuration': 1829,
'gameEndTimestamp': 1708908667752,
'gameId': 6832340332,
'gameMode': 'CLASSIC',
'gameName': 'teambuilder-match-6832340332',
'gameStartTimestamp': 1708906838802,
....
Function # 4 – Data of a specific player in the game
Given the match data and a player puuid, it returns only the data of the selected player.
def find_player_data(match_data, puuid):
participants = match_data['metadata']['participants']
player_index = participants.index(puuid)
player_data = match_data['info']['participants'][player_index]
return player_data
find_player_data(match_data, puuid)
{'allInPings': 0,
'assistMePings': 3,
'assists': 8,
'baronKills': 0,
'basicPings': 0,
'bountyLevel': 0,
'challenges': {'12AssistStreakCount': 0,
'abilityUses': 214,
'acesBefore15Minutes': 0,
'alliedJungleMonsterKills': 0,
'baronTakedowns': 0,
'blastConeOppositeOpponentCount': 0,
'bountyGold': 825,
'buffsStolen': 0,
'completeSupportQuestInTime': 0,
'controlWardTimeCoverageInRiverOrEnemyHalf': 0.1783287471126854,
'controlWardsPlaced': 2,
'damagePerMinute': 1012.4624544822548,
.....
We haven’t done anything new, we’ve just put everything into functions to make it easier to call them later.
Now, let’s say we want to look at our last 20 matches and extract the data. To do this, we simply loop through the list of match ids and run the functions. For my example, each time we will store information about the Champion, KDA and Results.
# We initialise an empty dictionary to store data for each game
data = {
'champion': [],
'kills': [],
'deaths': [],
'assists': [],
'win': []
}
for match_id in match_ids:
print(match_id)
# run the two functions to get the player data from the match ID
match_data = get_match_data(match_id, mass_region, api_key)
player_data = find_player_data(match_data, puuid)
# assign the variables we're interested in
champion = player_data['championName']
k = player_data['kills']
d = player_data['deaths']
a = player_data['assists']
win = player_data['win']
# add them to our dataset
data['champion'].append(champion)
data['kills'].append(k)
data['deaths'].append(d)
data['assists'].append(a)
data['win'].append(win)
EUW1_6832340332
EUW1_6832299147
EUW1_6829175370
EUW1_6829148496
EUW1_6829025866
EUW1_6828945398
EUW1_6828887595
EUW1_6828867611
EUW1_6828812533
EUW1_6824214120
EUW1_6824173176
EUW1_6824124368
EUW1_6823607057
EUW1_6823569193
EUW1_6816330441
EUW1_6816300579
EUW1_6814475777
EUW1_6814449666
EUW1_6811931211
EUW1_6811890150
# Data on the last 20 games of the account in question
data
{'champion': ['Hwei',
'LeeSin',
'Swain',
'Gnar',
'Lux',
'Pyke',
'Kindred',
'Lucian',
'Ezreal',
'Yasuo',
'Talon',
'Qiyana',
'TwistedFate',
'Sylas',
'Talon',
'TwistedFate',
'Briar',
'Hwei',
'Zed',
'Rengar'],
'kills': [10, 13, 12, 14, 6, 9, 5, 0, 8, 5, 7, 7, 3, 9, 11, 7, 6, 6, 10, 10],
'deaths': [10,
11,
7,
4,
14,
10,
8,
0,
0,
9,
2,
13,
7,
9,
4,
9,
17,
11,
13,
10],
'assists': [8,
11,
13,
13,
12,
4,
5,
0,
8,
10,
4,
9,
8,
9,
3,
6,
14,
13,
10,
10],
'win': [False,
False,
True,
True,
False,
False,
False,
True,
True,
False,
True,
False,
True,
True,
True,
False,
False,
False,
True,
False]}
To read this better, we will convert our data dictionary into a DataFrame.
df = pd.DataFrame(data)
df
We will also convert the above code into a function for later use.
def gather_all_data(puuid, match_ids, mass_region, api_key):
# We initialise an empty dictionary to store data for each game
data = {
'champion': [],
'kills': [],
'deaths': [],
'assists': [],
'win': []
}
for match_id in match_ids:
print(match_id)
# run the two functions to get the player data from the match ID
match_data = get_match_data(match_id, mass_region, api_key)
player_data = find_player_data(match_data, puuid)
# assign the variables we're interested in
champion = player_data['championName']
k = player_data['kills']
d = player_data['deaths']
a = player_data['assists']
win = player_data['win']
# add them to our dataset
data['champion'].append(champion)
data['kills'].append(k)
data['deaths'].append(d)
data['assists'].append(a)
data['win'].append(win)
df = pd.DataFrame(data)
return df
df = gather_all_data(puuid, match_ids, mass_region, api_key)
EUW1_6832340332
EUW1_6832299147
EUW1_6829175370
EUW1_6829148496
EUW1_6829025866
EUW1_6828945398
EUW1_6828887595
EUW1_6828867611
EUW1_6828812533
EUW1_6824214120
EUW1_6824173176
EUW1_6824124368
EUW1_6823607057
EUW1_6823569193
EUW1_6816330441
EUW1_6816300579
EUW1_6814475777
EUW1_6814449666
EUW1_6811931211
EUW1_6811890150
df
As you can see, the “win” column is a boolean (true or false).
For ease of use, we will convert it to an int (1 or 0)
df['win'] = df['win'].astype(int)
df
Now that we have it as a DataFrame, it is much easier to do what we want, for example:
# Searching for averages
df.mean(numeric_only=True) # numeric_only prevents it from trying to calculate the average of the "champion" column.
kills 7.90
deaths 8.40
assists 8.50
win 0.45
dtype: float64
# Get averages per champion
df.groupby('champion').mean()
# sort your games by number of kills
df.sort_values('kills')
Adding arguments to API calls
20 games is really not enough to evaluate an account, so let’s go back to one of our functions and add some arguments.
# The original function
def get_match_ids(puuid, mass_region, api_key):
api_url = (
"https://" +
mass_region +
".api.riotgames.com/lol/match/v5/matches/by-puuid/" +
puuid +
"/ids?start=0&count=20" +
"&api_key=" +
api_key
)
print(api_url)
resp = requests.get(api_url)
match_ids = resp.json()
return match_ids
As you can see in the original function there is already an argument for “count=20”, so all we have to do is replace it with a variable of our choice.
# Updated function where you can set how many games you want to play
def get_match_ids(puuid, mass_region, no_games, api_key):
api_url = (
"https://" +
mass_region +
".api.riotgames.com/lol/match/v5/matches/by-puuid/" +
puuid +
"/ids?start=0" +
"&count=" +
str(no_games) +
"&api_key=" +
api_key
)
print(api_url)
resp = requests.get(api_url)
match_ids = resp.json()
return match_ids
no_games = 25 # Leave it at 25 for now
match_ids = get_match_ids(puuid, mass_region, no_games, api_key)
print(len(match_ids))
https://EUROPE.api.riotgames.com/lol/match/v5/matches/by-puuid/3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ/ids?start=0&count=25&api_key=RGAPI-0f2ead89-905c-4393-8bd6-c0123456789
25
But let’s say we only want data of a certain queue type (I’ll use Classified Solos) How do we know which argument to use?
Go back to the portal, go back to MATCH-V5 (on the left) and open “/lol/match/v5/matches/by-puuid/{puuid}/ids” again.
Scroll to the bottom and fill in the form again with the same puuid, region and app Except this time, also add “420” to the input “queue” then EXECUTE REQUEST
Copy and paste the URL of the REQUEST:
'https://europe.api.riotgames.com/lol/match/v5/matches/by-puuid/3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ/ids?queue=420&start=0&count=20&api_key=RGAPI-552af7d9-284b-42d1-bec4-123456789'
As you can see, there is a new argument: “queue=420”.
If you are wondering where to find all the queue types, you can go here: https://static.developer.riotgames.com/docs/lol/queues.json
Just look for the queue type you want in the description and replace “queue” with the queueId.
Let’s add this to our function
# Updated function in which you can set from which queue to take the data.
def get_match_ids(puuid, mass_region, no_games, queue_id, api_key):
api_url = (
"https://" +
mass_region +
".api.riotgames.com/lol/match/v5/matches/by-puuid/" +
puuid +
"/ids?start=0" +
"&count=" +
str(no_games) +
"&queue=" +
str(queue_id) +
"&api_key=" +
api_key
)
print(api_url)
resp = requests.get(api_url)
match_ids = resp.json()
return match_ids
queue_id = 420
match_ids = get_match_ids(puuid, mass_region, no_games, queue_id, api_key)
match_ids
https://EUROPE.api.riotgames.com/lol/match/v5/matches/by-puuid/3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ/ids?start=0&count=25&queue=420&api_key=RGAPI-0f2ead89-905c-4393-8bd6-c0123456789
['EUW1_6832340332',
'EUW1_6832299147',
'EUW1_6829025866',
'EUW1_6828945398',
'EUW1_6828887595',
'EUW1_6828867611',
'EUW1_6828812533',
'EUW1_6824214120',
'EUW1_6824173176',
'EUW1_6824124368',
'EUW1_6823607057',
'EUW1_6823569193',
'EUW1_6816330441',
'EUW1_6816300579',
'EUW1_6811931211',
'EUW1_6811890150',
'EUW1_6811856975',
'EUW1_6811833682',
'EUW1_6811806415',
'EUW1_6811800693',
'EUW1_6811586364',
'EUW1_6811555500',
'EUW1_6811498704',
'EUW1_6809947413',
'EUW1_6809911341']
Let’s update the last function to match the speed limit
There is a limit to make requests to an API in Riot Games, currently it is set at 20 requests per second and 100 per 2 minutes. Then the maximum amount of recent games that can be requested is 100. And what happens if we request more ? It simply returns a 429 error (Rate limit exceeded), which means that we have exceeded our limit of requests.
So let’s extend the request to 100 games.
# Original function to obtain the party ID
def get_match_data(match_id, mass_region, api_key):
api_url = (
"https://" +
mass_region +
".api.riotgames.com/lol/match/v5/matches/" +
match_id +
"?api_key=" +
api_key
)
resp = requests.get(api_url)
print("Status Code:", resp.status_code) # We can read the status code directly from the object "resp".
match_data = resp.json()
return match_data
Let’s make a single main function to wrap everything up
We can group all our functions into one:
def master_function(summoner_name, region, mass_region, no_games, queue_id, api_key):
puuid = get_puuid(summoner_name, region, api_key, mass_region)
match_ids = get_match_ids(puuid, mass_region, no_games, queue_id, api_key)
df = gather_all_data(puuid, match_ids, mass_region, api_key)
return df
summoner_name = "IIM ONI"
region = "euw1"
mass_region = "EUROPE"
no_games = 100
queue_id = 420
df = master_function(summoner_name, region, mass_region, no_games, queue_id, api_key)
Now that we have a function that returns data for 100 sets, we can automate a bit by printing some information:
# prints some introductory stuff
print("Hello", summoner_name, "of", region.upper()) # upper just capitalizes the region
print("Here are some interesting statistics about your last 100 solo ranked games")
# create a count column
df['count'] = 1
# the "agg" allows us to get the average of each column but adding up the count # see?
champ_df = df.groupby('champion').agg({'kills': 'mean', 'deaths': 'mean', 'assists': 'mean', 'win': 'mean', 'count': 'sum'})
# reset in the index to be able to continue using the "champ" column
champ_df.reset_index(inplace=True)
# we limit it only to champions in which you have played 2 or more matches
champ_df = champ_df[champ_df['count'] >= 2]
# create a kda column
champ_df['kda'] = (champ_df['kills'] + champ_df['assists']) / champ_df['deaths']
# sort the table by KDA, starting with the highest one
champ_df = champ_df.sort_values('kda', ascending=False) # ascendente determina si es de mayor a menor o viceversa
# assign the first row and the last row to a variable in order to print information about it
best_row = champ_df.iloc[0] # .iloc[0] simply takes the first row of the data frame
worst_row = champ_df.iloc[-1] # .iloc[-1] takes the last row of a data frame
print("Your best KDA is on", best_row['champion'], "with a KDA of", best_row['kda'], "over", best_row['count'], "game/s")
print("Your worst KDA is on", worst_row['champion'], "with a KDA of", worst_row['kda'], "over", worst_row['count'], "game/s")
# sort by count
champ_df = champ_df.sort_values('count', ascending=False)
# get your most played champ
row = champ_df.iloc[0]
# assign and format the win rate
win_rate = row['win']]
win_rate = str(round(round(win_rate * 100, 1)) + "%"
print("Your highest played Champion is", row['champion'], "with", row['count'], 'game/s',
"and an average Win Rate of", win_rate)
# finally, sort by the highest number of kills in a game (note, not using the champ_df groupby anymore, but the raw data)
highest_kills = df.sort_values('kills', ascending=False)
row = highest_kills.iloc[0]
print("Your highest kill game was with", row['champion'], "where you had", row['kills'], "kills")
Hello IIM ONI of EUW1
Here are some interesting statistics about your last 100 solo ranked games
Your best KDA is on Renata with a KDA of 5.833333333333333 over 2 game/s
Your worst KDA is on Pyke with a KDA of 1.5 over 2 game/s
Your highest played Champion is Yasuo with 11 game/s and an average Win Rate of 72.7%
Your highest kill game was with Caitlyn where you had 19 kills
Data Visualization with Python
Using tools such as Pandas, Matplotlib and Seaborn, I created visualizations that showed interesting trends, statistics and patterns in the game data. These visualizations allowed me to share my findings effectively and better understand the world of LoL.
Number of games played per Champion
# Bar plot to show the number of games played with each champion
plt.figure(figsize=(10, 6))
sns.barplot(data=champ_df, x='champion', y='count', estimator=sum, palette='viridis')
plt.title('Number of Games Played per Champion')
plt.xlabel('Champion')
plt.ylabel('Number of Games')
plt.xticks(rotation=45)
plt.show()
Ratio of kills to deaths
# Scatter plot to show the relationship between kills and deaths
plt.figure(figsize=(8, 6))
sns.scatterplot(data=champ_df, x='kills', y='deaths', color='red', alpha=0.5)
plt.title('Relationship between Kills and Deaths')
plt.xlabel('Kills')
plt.ylabel('Deaths')
plt.show()
Average assists per champion
# Bar plot to show the average assists per champion
plt.figure(figsize=(10, 6))
sns.barplot(data=champ_df, x='champion', y='assists', estimator='mean', palette='magma')
plt.title('Average Assists per Champion')
plt.xlabel('Champion')
plt.ylabel('Average Assists')
plt.xticks(rotation=45)
plt.show()
KDA Distribution (Kills + Assists / Deaths)
# Histogram to show the distribution of KDA (Kills + Assists / Deaths)
plt.figure(figsize=(8, 6))
sns.histplot(data=champ_df, x='kda', bins=20, color='purple', kde=True)
plt.title('Distribution of KDA')
plt.xlabel('KDA')
plt.ylabel('Number of Games')
plt.show()
Number of kills per champion
# Bar plot to show the total number of kills per champion
plt.figure(figsize=(10, 6))
sns.barplot(data=champ_df, x='champion', y='kills', estimator=sum, palette='cool')
plt.title('Total Number of Kills per Champion')
plt.xlabel('Champion')
plt.ylabel('Number of Kills')
plt.xticks(rotation=45)
plt.show()
Number of deaths per champion
# Bar plot to show the total number of deaths per champion
plt.figure(figsize=(10, 6))
sns.barplot(data=champ_df, x='champion', y='deaths', estimator=sum, palette='flare')
plt.title('Total Number of Deaths per Champion')
plt.xlabel('Champion')
plt.ylabel('Number of Deaths')
plt.xticks(rotation=45)
plt.show()
Number of assists per champion
# Bar plot to show the total number of assists per champion
plt.figure(figsize=(10, 6))
sns.barplot(data=champ_df, x='champion', y='assists', estimator=sum, palette='rocket')
plt.title('Total Number of Assists per Champion')
plt.xlabel('Champion')
plt.ylabel('Number of Assists')
plt.xticks(rotation=45)
plt.show()
Number of games won and lost by champion
# Bar plot to show the number of games won and lost per champion
plt.figure(figsize=(10, 6))
sns.barplot(data=champ_df, x='champion', y='count', hue='win', estimator=sum, palette='husl')
plt.title('Number of Games Won and Lost per Champion')
plt.xlabel('Champion')
plt.ylabel('Number of Games')
plt.xticks(rotation=45)
plt.legend(title='Outcome')
plt.show()
KDA distribution by champion
# Box plot to show the distribution of KDA per champion
plt.figure(figsize=(10, 6))
sns.boxplot(data=champ_df, x='champion', y='kda', palette='pastel')
plt.title('Distribution of KDA per Champion')
plt.xlabel('Champion')
plt.ylabel('KDA')
plt.xticks(rotation=45)
plt.show()
Conclusion
My journey from being a fan of IM ONI to becoming a Data Science and Engineering professional is just beginning, but day by day it has been exciting and rewarding. Through analyzing and visualizing League of Legends data, I have been able to combine my passion for video games with my newly acquired technical skills. This journey has not only broadened my understanding of the game, but has also demonstrated the power and versatility of Data Science in various fields of interest. I am excited to continue exploring and learning in this exciting field, and look forward to sharing more of my findings with you in the future. Thank you for joining me on this journey!
Sources and resources
Jack J – iTero Gaming – Youtube
https://www.riotgames.com/en/DevRel/summoner-names-to-riot-id