Hola! Comparto con ustedes este viaje personal, como admirador de un gran jugador de League of Legends (LoL) llamado IM ONI, y cómo a través de los datos que ofrece el juego he logrado poco a poco avanzar en mi reconversión profesional hacia la Ciencia y la Ingeniería de Datos. En esta publicación, te llevaré a través de mi experiencia y destacaré cómo utilicé mis habilidades para analizar y visualizar datos del mundo de LoL.
Como muchos, me convertí en un gran admirador de IM ONI gracias a su habilidad en el mundo de LoL. Sin embargo, mi interés no se limitaba solo a ver sus partidas; también quería entender los datos detrás de su juego y descubrir patrones y tendencias que pudieran explicar su modo de juego. Este deseo de explorar y analizar datos me llevó a embarcarme en un viaje que conecta la extracción, transformación y carga – ETL- de datos de LOL, visualización y un primer análisis.

League of Legends (LoL): es un popular videojuego en línea de género MOBA (Multiplayer Online Battle Arena) desarrollado y publicado por Riot Games. En LoL, los jugadores controlan un personaje, conocido como «campeón», con habilidades únicas, y trabajan en equipo para destruir la base enemiga mientras defienden la suya propia. El juego se destaca por su enfoque en la estrategia, la cooperación en equipo y la habilidad individual.
Según el portal Active Player: en el momento de escribir esta publicación están jugando 1.050.863 personas online, en los últimos 30 días se han conectado más de 133.466.282 jugadores y más de 97.975.355 horas son vistas en Twitch con un pico de 712.950 espectadores simultaneos.
Es importante resaltar la ayuda del contenido de dos Youtubers CodinEric y Jack J de iTero Gaming que me han servido de referencia con sus videos y explicaciones. No se trataba solamente de seguir su código, sino entender el proceso y lógica de programación que usan para el tratamiento de los datos.
Configurando el entorno de desarrollo
Mi primer paso fue configurar mi entorno de desarrollo, instalando las bibliotecas necesarias de Python y obteniendo una clave de API de Riot Games para acceder a las APIs de LoL. Esta configuración inicial fue crucial para poder acceder a los datos del juego y comenzar mi análisis.
Entorno de desarrollo: utilicé JupyterLab para redactar el Notebook con Python de manera local. Siempre utilizo Conda para generar un ambiente e instalar las librerias según los requerimientos.
# 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
Clave de API de Riot Games: Ingresa a https://developer.riotgames.com/, después crea o inicia sesión con tu cuenta de Riot y acepta las condiciones del servicio. En la parte inferior, haz clic en «No soy un robot» y regenera tu clave API. Por políticas de seguridad de Riot Games, esta clave se desactiva cada 24 horas o menos según sus indicaciones, luego deberás repetir el procedimiento las veces necesarias durante el proyecto. Para fines prácticos, haré visible la «api_key» en el notebook, pero dejo claro que es una mala práctica.
A continuación copia la clave API y pégala en tu notebook bajo la variable «api_key», ejemplo:
api_key = 'RGAPI-0f2ead54-465c-2323-8bd6-c123456789'
Es muy importante entender que NO DEBES compartir tus claves de acceso en público, normalmente puedes utilizar variables de entorno que envuelvan tu clave y al momento de publicar tu código no sean visibles (yo lo entendí como un GitIgnore, para no subir o hacer visible lo que no quieres).
# 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.
Utilidad de Consumir la API de League of Legends
Consumir la API de League of Legends proporciona acceso a una gran cantidad de datos sobre el juego, los jugadores y las partidas. Estos datos pueden ser utilizados para una variedad de propósitos, incluyendo:
- Análisis de Rendimiento de Jugadores: Los datos de las partidas pueden utilizarse para analizar el rendimiento de los jugadores, identificar áreas de mejora y comparar estadísticas con otros jugadores.
- Estudio de Estrategias y Tendencias: Analizar los datos de las partidas permite identificar tendencias en la selección de campeones, estrategias de juego y resultados de las partidas, lo que puede ayudar a los jugadores a mejorar su estrategia y adaptarse al meta actual.
- Visualización de Datos: Los datos del juego pueden ser visualizados de diversas formas, como gráficos y tablas interactivas, para comprender mejor las dinámicas del juego, compartir hallazgos y realizar informes detallados.
- Investigación Académica: Los datos de League of Legends también pueden ser utilizados para fines académicos, como estudios sobre comportamiento de los jugadores, análisis de redes sociales y modelado de sistemas complejos.
En resumen, consumir la API de League of Legends proporciona una valiosa fuente de datos para el análisis y comprensión del juego, así como para la mejora del rendimiento individual y colectivo de los jugadores.
Puedes acceder a toda la documentación del modo de consumo de APIs de LOL en https://developer.riotgames.com/docs/lol
Consulta de datos de jugadores
Utilizando las APIs de LoL, pude obtener información detallada sobre los jugadores, incluyendo su nombre de invocador, nivel, historial de partidas y más. Esto me permitió estudiar el rendimiento de diferentes jugadores, incluido IM ONI.
En un inicio, solamente conocemos el nombre del jugador «invocador» y la región (servidor) donde juega, para nuestro caso: IM ONI y euw (servidor en Europa).
Hasta noviembre de 2023 se podía utilizar la API «Summoner-V4» en su endpoint "/lol/summoner/v4/summoners/by-name/{summonerName}" para obtener la identificación del jugador. Riot Games utiliza tres (3) diferentes Ids para seguirlo: El PUUID, el SummonerId y el AccountId. En realidad, los dos primeros están vigentes y son necesarios para hacer consultas según el endpoint (Ubicación de la API en la que un sistema interactúa con una API web.).
Actualmente, Riot Games sugiere utilizar la API «Account-v1» para obtener el PUUID (Identificadores únicos universales de jugador), es de tipo string (hasta 78 caracteres) para rastrear el jugador en todo el mundo, incluso si cambia de region (servidor).
Para obtenerlo vamos al endpoint "/riot/account/v1/accounts/by-riot-id/{gameName}/{tagLine}" en la API "Account-V1" que puedes encontrar al hacer login con tu cuenta en developer.riotgames.com/apis. En la sección «Path parameters» inserta en «tagLine» = euw1 (región del jugador) y en «gameName» = IIM ONI (nombre del jugador), selecciona EUROPE (o la región del jugador) y ejecuta la consulta. Esto generará una URL con los datos de vuelta.

Copias la URL que genera y la pegas en tu notebook como una nueva 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"
Luego, con la librería «requests» ya instalada validamos la conexión así:
requests.get(api_url)
<Response [200]>
Deberías tener una respuesta [200] como conexión exitosa. Si tienes una respuesta de error en conexión tipo «400, 401, etc.» valida en la lista de errores de respuesta de cada endpoint. (Usualmente te marcará error porque la api key expiró y tendrás que crear una nueva)
Después podrás acceder a la información en formato Json y guardarlo en una variable.
resp = requests.get(api_url)
player_info = resp.json()
player_info
$ {'puuid': '3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ',
'gameName': 'IIM ONI',
'tagLine': 'EUW'}
Ahora con el PUUID, obtenemos el SUMMONER ID con el endpoint "/lol/summoner/v4/summoners/by-puuid/{encryptedPUUID}"
Hacemos el mismo procedimiento en la API «Summoner-V4» y obtenemos una nueva 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}
Ya con el «Puuid» y el «SummonerId» podremos hacer consultas a otras APIS como «League-V4» o «Champion-mastery«
Obtención de Datos de Partidas
Explorar los datos de las partidas recientes fue especialmente emocionante. Pude acceder a información detallada sobre las partidas, como los campeones seleccionados, los resultados de la partida y las estadísticas de juego. Esto me permitió identificar tendencias y estrategias comunes entre los jugadores de las partidas. Recordemos que LOL es un juego de cinco contra cinco, pero un jugador puede competir solo y subir su nivel.
Lo que primero te preguntan cuando juegas LOL es cúal es tu nivel de clasificación, entonces para encontrar el ranking de tu jugador vamos a consultar la API «League-v4» en el endpoint "/lol/league/v4/entries/by-summoner/{encryptedSummonerId}" esta información será el 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]]) # crear dataframe con Ranking Mastery
df_rank # Ver el dataframe.

Ahora es el turno de los datos de partidas, para eso utilizaremos la API «Match-v5» con el PUUID que obtuvimos.
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']
Filtramos la partida más reciente:
recent_match = match_ids[0]
recent_match
'EUW1_6832340332'
Utilizamos la API «/lol/match/v5/matches/{matchId}» para extraer la data del juego con su 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
Esto nos devuelve un archivo Json de 2823 líneas de código. Aquí las primeras 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
Obteniendo la data de Im ONI:
Tomamos la lista de puuids de todos los participantes y usamos «index» para averiguar dónde está nuestro jugador 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
Esto debería coincidir con el puuid que usamos para buscar los IDs coincidentes.
participants[player_index]
'3U7K7AmFQldQCNlJ9m5OwFRhrIrJ6ZsVMpdhS9HDoTj7BlUsjYDTcVeZnYdU8dMudHzxT5yspRkNkQ'
Comprobemos que funciona utilizando este índice para obtener el nombre de los jugadores a partir de los datos.
# 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'
Exploración de Datos de Campeones
Otro aspecto interesante fue la exploración de datos sobre los campeones disponibles en LoL. Consulté información sobre estadísticas base, habilidades y más, lo que me ayudó a comprender mejor las fortalezas y debilidades de cada campeón y su impacto en el juego.
Utilizamos la API Champion Mastery "/lol/champion-mastery/v4/champion-masteries/by-puuid/{encryptedPUUID}" que nos devuelve el siguiente 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)
Ahora podremos analizar más adelante este dataframe con series de tiempo en Pandas.
Recopilación de datos
Aprender a conectarse con las APIs, hacer las consultas y obtener los datos es un proceso que aún me cuesta entender en varios puntos. Al final, nuestro objetivo es recopilar la mayor cantidad de datos de nuestro jugador IM ONI, y aunque la curva de aprendizaje es fuerte, la forma más rápida y ordenada es creando funciones. Para entender la estructura y utilidad de cada Función utilicé los canales citados de Youtube, webs de referencia sobre contenidos de LOL, la documentación oficial del juego y algunos ejercicios realizados dentro de la ruta de aprendizaje en Ingeniería de Datos con Platzi.
Función #1 – Obtener el PUUID de un jugador
La primera función simplemente obtiene el puuid, dado un nombre de invocador y una región.
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'
Lo que hicimos fue construir la url de la API, hacemos la consulta y obtenemos el PUUID. Es lo mismo que nuestro primer paso, solo que ahora está modularizado y optimizado.
Function #2 – Lista de Ids de los últimos 20 partidos
La función para obtener una lista de todos los ID de partido (2º ejemplo anterior) dado un puuid de jugador y el 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']
Función # 3 – Datos de una partida
A partir de un ID de partido y una región de masa dados, obtener los datos sobre el partido.
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,
....
Función # 4 – Datos de un jugador específico de la partida
Dados los datos del partido y un puuid de jugador, devuelve únicamente los datos del elegido.
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,
.....
No hemos hecho nada nuevo, sólo hemos puesto todo en funciones para que sea más fácil llamarlas más tarde.
Ahora, digamos que queremos mirar nuestros últimos 20 partidos y extraer los datos. Para ello, simplemente hacemos un bucle a través de la lista de ids de partidos y ejecutamos las funciones. Para mi ejemplo, cada vez guardaremos información sobre el Campeón, KDA y Resultados.
# 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]}
Para leer esto mejor, convertiremos nuestro diccionario de datos en un DataFrame.
df = pd.DataFrame(data)
df

También convertiremos el código anterior en una función para su uso posterior.
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

Como puede ver, la columna «win» es un booleano (verdadero o falso).
Para facilitar su uso, lo convertiremos en un int (1 o 0)
df['win'] = df['win'].astype(int)
df

Ahora que lo tenemos como un DataFrame, es mucho más fácil hacer lo que queremos, por ejemplo:
# 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')

Añadir argumentos a las llamadas a la API
20 juegos realmente no es suficiente para evaluar una cuenta, así que volvamos a una de nuestras funciones y añadamos algunos argumentos.
# 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
Como puedes ver en la función original ya hay un argumento para «count=20», así que todo lo que tenemos que hacer es sustituirla por una variable de nuestra elección.
# 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
Pero digamos que sólo queremos datos de un determinado tipo de cola (usaré Solos Clasificados) ¿Cómo sabemos qué argumento utilizar?
Vuelve al portal, regresa a MATCH-V5 (a la izquierda) y abre de nuevo «/lol/match/v5/matches/by-puuid/{puuid}/ids» \
Desplázate hasta la parte inferior y vuelve a rellenar el formulario con el mismo puuid, región y app Excepto esta vez, también añadir «420» a la «cola» de entrada a continuación, EJECUTAR SOLICITUD
Copia y pegua la URL de la SOLICITUD:
'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'
Como puedes ver, hay un nuevo argumento: «queue=420»
Si te preguntas dónde encontrar todos los tipos de cola, puedes ir aquí: https://static.developer.riotgames.com/docs/lol/queues.json
Simplemente busca el tipo de cola que quieras en la descripción y sustituye «queue» por el queueId.
Añadamos esto a nuestra función
# 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']
Actualicemos la última función para que se ajuste al límite de velocidad
Existe un límite para realizar solicitudes a una API en Riot Games, actualmente se establece en 20 solicitudes por segundo y 100 por 2 minutos. Luego la cantidad máxima de juegos recientes que se pueden solicitar es 100. Y qué pasa si solicitamos más ? Simplemente nos devuelve un error 429 (Rate limit exceded), lo que significa es que hemos superado nuestro límite de peticiones.
Así que vamos a ampliar la solicitud a 100 partidas.
# 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
Hagamos una sola función principal para envolver todo
Podemos agrupar todas nuestras funciones en una sola:
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)
Ahora que tenemos una función que nos devuelve los datos de 100 juegos, podemos automatizar un poco imprimiendo alguna información:
# 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
Visualización de Datos con Python
Utilizando herramientas como Pandas, Matplotlib y Seaborn, creé visualizaciones que mostraban tendencias, estadísticas y patrones interesantes en los datos del juego. Estas visualizaciones me permitieron compartir mis hallazgos de manera efectiva y comprender mejor el mundo de LoL.
Número de partidas jugadas por Campeón

# 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()
Relación entre asesinatos (kills) y muertes (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()
Promedios de asistencias (assists) por campeón

# 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()
Distribución del KDA (Asesinatos + Asistencias / Muertes)

# 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()
Número de asesinatos (kills) por campeón

# 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()
Número de muertes (deaths) por campeón

# 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()
Número de asistencias (assists) por campeón

# 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()
Número de partidas ganadas y perdidas por campeón

# 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()
Distribución de KDA por campeón

# 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()
Conclusión
Mi viaje desde ser un admirador de IM ONI hasta convertirme en un profesional en Ciencia e Ingeniería de Datos hasta ahora comienza, pero dia a dia ha sido emocionante y gratificante. A través del análisis y la visualización de datos de League of Legends, he podido combinar mi pasión por los videojuegos con mis habilidades técnicas recién adquiridas. Este viaje no solo ha ampliado mi comprensión del juego, sino que también ha demostrado el poder y la versatilidad de la Ciencia de Datos en diversos campos de interés. Estoy emocionado de continuar explorando y aprendiendo en este apasionante campo, y espero compartir más de mis hallazgos con ustedes en el futuro. ¡Gracias por acompañarme en este viaje!
Fuentes y recursos
Jack J – iTero Gaming – Youtube
https://www.riotgames.com/en/DevRel/summoner-names-to-riot-id

