Aller au contenu (Pressez Entrée)
Javier Ladino

Javier Ladino

{Data/Design/Engineer}

  • Accueil
  • Portfolio
  • A propos de moi
  • EN 🇺🇸
  • ES 🇨🇴
DataViz

Découvrir le monde de League of Legends grâce aux données

Bonjour, je partage avec vous ce parcours personnel, en tant que fan d’un grand joueur de League of Legends (LoL) appelé IM ONI, et comment à travers les données que le jeu offre, j’ai lentement réussi à avancer dans ma reconversion professionnelle vers la Data Science et l’Ingénierie des Données. Dans ce billet, je vais vous faire découvrir mon expérience et mettre en lumière la manière dont j’ai utilisé mes compétences pour analyser et visualiser des données issues du monde de LoL.

Comme beaucoup, je suis devenu un grand fan d’IM ONI en raison de leur expertise dans le monde de LoL. Cependant, mon intérêt ne s’est pas limité à regarder ses matchs ; j’ai également voulu comprendre les données qui sous-tendent son jeu et découvrir des modèles et des tendances qui pourraient expliquer son jeu. Ce désir d’explorer et d’analyser les données m’a conduit à m’embarquer dans un voyage qui relie l’extraction, la transformation et le chargement – ETL – des données LOL, la visualisation et une première analyse.

Im ONI
Twitch – Im ONI – LoL

League of Legends (LoL): est un jeu vidéo en ligne populaire du genre MOBA (Multiplayer Online Battle Arena) développé et publié par Riot Games. Dans LoL, les joueurs contrôlent un personnage, appelé « champion », doté de capacités uniques, et travaillent en équipe pour détruire la base ennemie tout en défendant la leur. Le jeu se distingue par l’importance qu’il accorde à la stratégie, à la coopération au sein de l’équipe et aux compétences individuelles.

Selon le portail Active Player: à l’heure où nous écrivons ces lignes, 1 050 863 personnes jouent en ligne, au cours des 30 derniers jours, plus de 133 466 282 joueurs se sont connectés et plus de 97 975 355 heures ont été regardées sur Twitch, avec un pic de 712 950 spectateurs simultanés.

Il est important de souligner l'aide du contenu de deux Youtubers CodinEric et Jack J de iTero Gaming qui m'ont servi de référence avec leurs vidéos et leurs explications. Il ne s'agissait pas seulement de suivre leur code, mais aussi de comprendre le processus et la logique de programmation qu'ils utilisent pour traiter les données.

Configuration de l’environnement de développement

Ma première étape a été de mettre en place mon environnement de développement, en installant les bibliothèques Python nécessaires et en obtenant une clé API de Riot Games pour accéder aux API de LoL. Cette configuration initiale était cruciale pour pouvoir accéder aux données du jeu et commencer mon analyse.

Environnement de développement : j’ai utilisé JupyterLab pour écrire le Notebook avec Python localement. J’utilise toujours Conda pour générer un environnement et installer les bibliothèques en fonction des besoins.

# 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

Clé API de Riot Games : rendez-vous sur https://developer.riotgames.com/, puis créez ou connectez-vous avec votre compte Riot et acceptez les conditions d’utilisation. En bas de la page, cliquez sur « I’m not a robot » et régénérez votre clé API. En raison des politiques de sécurité de Riot Games, cette clé est désactivée toutes les 24 heures ou moins selon les instructions de Riot Games, vous devrez donc répéter la procédure autant de fois que nécessaire pendant le projet. Pour des raisons pratiques, je rendrai la « api_key » visible sur le notebook, mais je précise qu’il s’agit d’une mauvaise pratique.

Copiez donc la clé API et collez-la dans votre notebook sous la variable « api_key », exemple :

api_key = 'RGAPI-0f2ead54-465c-2323-8bd6-c123456789'

Il est très important de comprendre que vous NE DEVEZ PAS partager vos mots de passe en public, normalement vous pouvez utiliser des variables d'environnement qui contiennent votre mot de passe et au moment de la publication de votre code elles ne sont pas visibles (je l'ai compris comme un GitIgnore, pour ne pas télécharger ou rendre visible ce que vous ne voulez pas).

# 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.

Utilitaire de consommation de l’API de League of Legends

L’utilisation de l’API League of Legends permet d’accéder à une multitude de données sur le jeu, les joueurs et les parties. Ces données peuvent être utilisées à diverses fins, notamment :

  1. Analyse des performances des joueurs : les données de jeu peuvent être utilisées pour analyser les performances des joueurs, identifier les points à améliorer et comparer les statistiques avec celles d’autres joueurs.
  2. Recherche de stratégies et de tendances : l’analyse des données de jeu permet d’identifier les tendances dans la sélection des champions, les stratégies de jeu et les résultats de jeu, ce qui peut aider les joueurs à améliorer leur stratégie et à s’adapter à la méta actuelle.
  3. Visualisation des données : Les données de jeu peuvent être visualisées de différentes manières, telles que des graphiques interactifs, afin de mieux comprendre les dynamiques de jeu, de partager les résultats et de rédiger des rapports détaillés.
  4. Recherche universitaire : les données de League of Legends peuvent également être utilisées à des fins universitaires, telles que l’étude du comportement des joueurs, l’analyse des réseaux sociaux et la modélisation de systèmes complexes.

En résumé, l’utilisation de l’API de League of Legends constitue une source précieuse de données pour analyser et comprendre le jeu, ainsi que pour améliorer les performances individuelles et collectives des joueurs.

Une documentation complète sur la manière d’utiliser les API de LOL est disponible à l’adresse suivante : https://developer.riotgames.com/docs/lol.

Interrogation des données des joueurs

En utilisant les API de LoL, j’ai pu obtenir des informations détaillées sur les joueurs, notamment leur nom d’invocateur, leur niveau, leur historique de jeu, etc. Cela m’a permis d’étudier les performances de différents joueurs, dont IM ONI.

Au départ, nous ne connaissons que le nom du joueur « invocateur » et la région (serveur) où il joue, dans notre cas : IM ONI et euw (serveur en Europe).

Jusqu’en novembre 2023, vous pouvez utiliser l’API « Summoner-V4 » dans son point de terminaison « /lol/summoner/v4/summoners/by-name/{summonerName} » pour obtenir l’ID du joueur. Riot Games utilise trois (3) identifiants différents pour le suivre : le PUUID, le SummonerId et le AccountId. En fait, les deux premiers sont en vigueur et sont nécessaires pour effectuer des requêtes en fonction du point de terminaison (emplacement de l’API où un système interagit avec une API web).

Actuellement, Riot Games propose d’utiliser l’API « Account-v1 » pour obtenir le PUUID (Player Universal Unique Identifiers), qui est une chaîne (jusqu’à 78 caractères) permettant de suivre le joueur dans le monde entier, même s’il change de région (serveur).

Pour l’obtenir, allez au point de terminaison « /riot/account/v1/accounts/by-riot-id/{gameName}/{tagLine} » dans l’API « Account-V1 » que vous pouvez trouver lorsque vous vous connectez avec votre compte sur developer.riotgames.com/apis. Dans la section « Path parameters » insérez dans « tagLine » = euw1 (région du joueur) et dans « gameName » = IIM ONI (nom du joueur), sélectionnez EUROPE (ou la région du joueur) et exécutez la requête. Cela générera une URL avec les données en retour.

Vous copiez l’URL qu’il génère et la collez dans votre notebook en tant que nouvelle 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"

Ensuite, avec la bibliothèque « requests » déjà installée, nous validons la connexion comme suit :
requests.get(api_url)
<Response [200]>

Vous devriez obtenir une réponse [200] comme une connexion réussie. Si vous obtenez une réponse d’erreur de connexion telle que « 400, 401, etc. », validez dans la liste des réponses d’erreur de chaque point de terminaison (en général, vous obtiendrez une erreur parce que la clé api a expiré et que vous devrez en créer une nouvelle).

Vous pouvez ensuite accéder aux informations au format Json et les enregistrer dans une variable.

resp = requests.get(api_url)
player_info = resp.json()
player_info
$ {'puuid': '3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ',
 'gameName': 'IIM ONI',
 'tagLine': 'EUW'}

Maintenant, avec le PUUID, nous obtenons l’ID SUMMONER avec le point de terminaison « /lol/summoner/v4/summoners/by-puuid/{encryptedPUID}« .

Nous effectuons la même procédure dans l’API « Summoner-V4 » et obtenons une nouvelle 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}

Avec le « Puuid » et le « SummonerId« , nous pourrons interroger d’autres APIS comme « League-V4 » ou « Champion-mastery« .

Collecte de données sur les jeux

L’exploration des données de jeux récents a été particulièrement passionnante. J’ai pu accéder à des informations détaillées sur les parties, telles que les champions sélectionnés, les résultats des parties et les statistiques des parties. Cela m’a permis d’identifier des tendances et des stratégies communes parmi les joueurs des parties. N’oubliez pas que LOL est un jeu à cinq contre cinq, mais qu’un joueur peut s’affronter seul et monter en niveau.

La première chose que l’on vous demande lorsque vous jouez à LOL est quel est votre niveau de classement, donc pour trouver le classement de votre joueur, nous allons interroger l’API « League-v4 » dans le point de terminaison « /lol/league/v4/entries/by-summoner/{encryptedSummonerId}« , cette information sera le Ranking Mastery.

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

C’est maintenant au tour des données de match, pour cela nous utiliserons l’API « Match-v5 » avec le PUUID que nous avons obtenu.

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']

Nous filtrons les départs les plus récents :

recent_match = match_ids[0]
recent_match
'EUW1_6832340332'

Nous utilisons l’API « /lol/match/v5/matches/{matchId} » pour extraire les données du jeu avec son identifiant.

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

Cela renvoie un fichier Json de 2823 lignes de code. Voici les 20 premières :

{'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

Obtention de données auprès d’Im ONI :

Nous prenons la liste des puuid de tous les participants et utilisons « index » pour savoir où se trouve notre joueur Im ONI.

# 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

Il doit correspondre au puuid que nous utilisons pour rechercher les identifiants correspondants.

participants[player_index]
'3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ'

Vérifions que cela fonctionne en utilisant cet index pour obtenir le nom des joueurs à partir des données.

# 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'

Exploration des données sur les champions

Un autre aspect intéressant a été l’exploration des données sur les champions disponibles dans LoL. J’ai consulté des informations sur les statistiques de base, les compétences et bien plus encore, ce qui m’a permis de mieux comprendre les forces et les faiblesses de chaque champion et leur impact sur le jeu.

Nous avons utilisé l’API de maîtrise des champions « /lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUID} » qui renvoie le point de terminaison suivant :

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
Dataframe Champion Mastery
# 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)

Nous allons maintenant pouvoir analyser ce cadre de données avec des séries temporelles dans Pandas.

Collecte de données

Apprendre à se connecter aux API, à faire des requêtes et à obtenir des données est un processus que j’ai encore du mal à comprendre à plusieurs égards. En fin de compte, notre objectif est de collecter autant de données que possible à partir de notre lecteur IM ONI, et bien que la courbe d’apprentissage soit raide, la façon la plus rapide et la plus ordonnée est de créer des fonctions. Pour comprendre la structure et l’utilité de chaque fonction, j’ai utilisé les chaînes Youtube mentionnées, des sites web de référence sur le contenu LOL, la documentation officielle du jeu et quelques exercices réalisés dans le cadre du parcours d’apprentissage en Ingénierie des données avec Platzi.

Fonction #1 – Obtenir le PUUID d’un joueur

La première fonction permet simplement d’obtenir le puuid, en fonction du nom de l’appelant et de la région.

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'

Ce que nous avons fait, c’est construire l’url de l’API, effectuer la requête et obtenir le PUUID. C’est la même chose que notre première étape, mais elle est maintenant modulaire et optimisée.

Fonction #2 – Liste des identifiants des 20 derniers matches

La fonction permettant d’obtenir une liste de tous les ID de match (2ème exemple ci-dessus) à partir d’un puuid de joueur et de la 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']

Fonction # 3 – Données du jeu

À partir d’un identifiant de parti et d’une région de masse donnés, obtenir les données sur le parti.

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,
  ....

Fonction n° 4 – Données du joueur spécifiques au jeu

Étant donné les données du match et un puuid de joueur, il renvoie uniquement les données du joueur sélectionné.

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,
.....

Nous n’avons rien fait de nouveau, nous avons simplement placé toutes les données dans des fonctions afin de pouvoir les appeler plus facilement par la suite.

Supposons maintenant que nous voulions regarder nos 20 derniers matchs et en extraire les données. Pour ce faire, il suffit de parcourir en boucle la liste des identifiants de match et d’exécuter les fonctions. Dans mon exemple, nous enregistrerons à chaque fois des informations sur le champion, le KDA et les résultats.

# 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]}

Pour une meilleure lecture, nous allons convertir notre dictionnaire de données en DataFrame.

df = pd.DataFrame(data)
df

Nous allons également convertir le code ci-dessus en une fonction pour une utilisation ultérieure.

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

Comme vous pouvez le constater, la colonne « win » est un booléen (vrai ou faux).
Pour plus de facilité, nous allons la convertir en un int (1 ou 0).

df['win'] = df['win'].astype(int)
df

Maintenant que nous l’avons sous forme de DataFrame, il est beaucoup plus facile de faire ce que nous voulons, par exemple :

# 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')

Ajouter des arguments aux appels d’API

20 jeux n’est pas vraiment suffisant pour évaluer un compte, alors retournons à l’une de nos fonctions et ajoutons quelques 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 

Comme vous pouvez le voir dans la fonction originale, il y a déjà un argument pour « count=20 », donc tout ce que nous avons à faire est de le remplacer par une variable de notre choix.

# 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

Mais si nous ne voulons que les données d’un certain type de file d’attente (j’utiliserai les solos classifiés), comment savoir quel argument utiliser ?

Retournez sur le portail, revenez à MATCH-V5 (à gauche) et rouvrez « /lol/match/v5/matches/by-puuid/{puuid}/ids« .

Descendez jusqu’en bas et remplissez à nouveau le formulaire avec le même puuid, la même région et la même application. Sauf que cette fois-ci, ajoutez également « 420 » à l’entrée « queue » puis EXECUTE REQUEST

Copiez et collez l’URL de la demande :

'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'

Comme vous pouvez le voir, il y a un nouvel argument : « queue=420 ».

Si vous vous demandez où trouver tous les types de file d’attente, vous pouvez aller ici : https://static.developer.riotgames.com/docs/lol/queues.json

Recherchez simplement le type de file d’attente que vous souhaitez dans la description et remplacez « queue » par l’identifiant de la file d’attente.

Ajoutons ceci à notre fonction

# 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']

Mettons à jour la dernière fonction pour qu’elle corresponde à la limite de vitesse.

Il y a une limite pour les requêtes API sur Riot Games, actuellement elle est fixée à 20 requêtes par seconde et 100 par 2 minutes. Le nombre maximum de jeux récents pouvant être demandés est donc de 100. Que se passe-t-il si nous en demandons plus ? Il renvoie simplement une erreur 429 (Rate limit exceeded), ce qui signifie que nous avons dépassé notre limite de demande.

Étendons donc la demande à 100 jeux.

# 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     

Nous allons créer une fonction principale pour conclure le tout

Nous pouvons regrouper toutes nos fonctions en une seule :

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)

Maintenant que nous disposons d’une fonction qui renvoie les données de 100 jeux, nous pouvons automatiser un peu les choses en imprimant certaines informations :

# 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

Visualisation de données avec Python

À l’aide d’outils tels que Pandas, Matplotlib et Seaborn, j’ai créé des visualisations montrant des tendances, des statistiques et des schémas intéressants dans les données du jeu. Ces visualisations m’ont permis de partager efficacement mes résultats et de mieux comprendre le monde de LoL.

Nombre de matchs joués par 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()

Relation entre les tués et les morts

# 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()

Nombre moyen de passes décisives par 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()

Distribution des KDA (Killings + 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()

Nombre de morts par 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()

Nombre de décès par 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()

Nombre de passes décisives par 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()

Nombre de matchs gagnés et perdus par le 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()

Répartition des KDA par 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

Mon parcours de fan d’IM ONI à professionnel de la science des données et de l’ingénierie ne fait que commencer, mais il a été passionnant et gratifiant jour après jour. Grâce à l’analyse et à la visualisation des données de League of Legends, j’ai pu combiner ma passion pour les jeux vidéo avec mes compétences techniques nouvellement acquises. Ce voyage a non seulement élargi ma compréhension du jeu, mais a également démontré la puissance et la polyvalence de la science des données dans divers domaines d’intérêt. Je suis enthousiaste à l’idée de continuer à explorer et à apprendre dans ce domaine passionnant, et j’ai hâte de partager mes découvertes avec vous à l’avenir. Merci de m’avoir accompagné dans cette aventure !

Sources et ressources

CodinEric – Youtube

Jack J – iTero Gaming – Youtube

https://www.riotgames.com/en/DevRel/summoner-names-to-riot-id

https://developer.riotgames.com

https://support-leagueoflegends.riotgames.com/hc/es-419/articles/4406004330643-Niveles-de-clasificatoria-divisiones-y-colas

https://darkintaqt.com/blog/ids

Projets similaires

A la découverte de la richesse végétale de Nantes : Un voyage à travers sa collection botanique

septembre 2, 2024

Créer une empreinte chromatique à partir de Flickr en utilisant Python

mai 25, 2023

🦉Les oiseaux de Bogotá 🐦

avril 29, 2022
© Copyright 2026 Javier Ladino. Perfect Portfolio | Développé par Rara Theme. Propulsé par WordPress.