League of Legends Match Outcome Predictor Using Machine Learning

AI/ML 2024-10-29 Python 22 min read

My aim in this project was to predict match outcomes in League of Legends by leveraging machine learning models trained on recent match data. By Riot Games' API to gather historical match data and apply KNN to analyze the win ratio with respect to team compositions and match results.

1. Importing Libraries and Configuring API

from riotwatcher import LolWatcher, ApiError
from itertools import permutations
from os import path
import numpy as np
import csv
import json
import requests
import pandas as pd
import time

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

API_TOKEN = '?api_key='  # Insert your API key here
HOST = 'https://tr1.api.riotgames.com/'  # Server location, e.g., Turkey region

Required project imports of libraries for API requests, data handling, and machine learning. Pandas and NumPy manage data, while scikit-learn offer models and evaluation metrics for prediction. The API token and host are for enabling access to Riot Games' API to get historical match data.

2. LOLAPI Class - Managing API Interactions

class LOLAPI:
    def __init__(self, username, host=HOST, token=API_TOKEN):
        self.host = host
        self.token = token
        self.username = username

    def get_encrypted_account_id(self):
        url = f"{self.host}/lol/summoner/v4/summoners/by-name/{self.username}{self.token}"
        response = requests.get(url).json()
        return response['accountId']

    def get_matches(self, account_id):
        url = f"{self.host}/lol/match/v4/matchlists/by-account/{account_id}{self.token}"
        return requests.get(url).json().get('matches', [])

    def get_match_info(self, match_id):
        url = f"{self.host}/lol/match/v4/matches/{match_id}{self.token}"
        return requests.get(url).json()

    def get_champions(self):
        url = "http://ddragon.leagueoflegends.com/cdn/10.18.1/data/en_US/champion.json"
        response = requests.get(url).json()
        return {int(data['key']): champ for champ, data in response['data'].items()}

The LOLAPI class manages the interaction with the Riot Games API, providing methods to retrieve account IDs, match lists, individual match details, and champion information. The API calls are modularized to enable easy access and reuse.

3. MatchDataProcessor Class - Processing and Extracting Match Data

class MatchDataProcessor:
    def __init__(self, api):
        self.api = api

    def process_match_data(self, match_data):
        results, labels = [], []
        matches = {match['gameId']: match['champion'] for match in match_data}
        
        for counter, (match_id, champ_id) in enumerate(matches.items(), 1):
            if counter % 10 == 0:
                break

            match_info = self.api.get_match_info(match_id)
            if 'status' in match_info:
                break

            match_features, match_label = self.extract_match_features(match_info, champ_id)
            if match_features:
                results.append(match_features)
                labels.append(match_label)
        return results, labels

    def extract_match_features(self, match_info, champion_id):
        participant_data = {p['championId']: p for p in match_info['participants']}
        team_id = participant_data[champion_id]['teamId']
        win = participant_data[champion_id]['stats']['win']

        ally_champs = [p['championId'] for p in match_info['participants'] if p['teamId'] == team_id and p['championId'] != champion_id]
        enemy_team_id = (team_id % 200) + 100
        enemy_champs = [p['championId'] for p in match_info['participants'] if p['teamId'] == enemy_team_id]

        if all(champ == enemy_champs[0] for champ in enemy_champs):
            enemy_champs = []

        if ally_champs and enemy_champs:
            features = {
                'GameId': match_info['gameId'],
                'ChampionId': champion_id,
                'Allies': ally_champs,
                'Enemies': enemy_champs
            }
            return features, int(win)
        return None, None

The MatchDataProcessor class processes match data by extracting relevant features (champion, allies, and enemies) for each match. It includes error handling for rate limits and custom feature extraction for each match, allowing flexibility and efficient data preparation.

4. CSVHandler Class - Saving and Loading Processed Data

class CSVHandler:
    @staticmethod
    def save_to_csv(filename, match_data):
        filepath = f"./{filename}.csv"
        header = ["MatchId", "ChampionId", "Ally1", "Ally2", "Ally3", "Ally4", "Enemy1", "Enemy2", "Enemy3", "Enemy4", "Enemy5"]
        
        with open(filepath, mode="w", newline="") as file:
            writer = csv.writer(file)
            writer.writerow(header)
            for match in match_data:
                row = [match['GameId'], match['ChampionId']] + match['Allies'] + match['Enemies'][:5]
                writer.writerow(row)

    @staticmethod
    def load_from_csv(filename):
        filepath = f"./{filename}.csv"
        if not path.exists(filepath):
            return [], []
        
        with open(filepath, mode="r", newline="") as file:
            reader = csv.reader(file)
            next(reader)  # Skip header
            matches, labels = [], []
            for row in reader:
                matches.append({
                    'GameId': row[0],
                    'ChampionId': int(row[1]),
                    'Allies': list(map(int, row[2:6])),
                    'Enemies': list(map(int, row[6:11]))
                })
                labels.append(int(row[11]))
        return matches, labels

The CSVHandler class provides methods to save processed data to CSV and load it back for analysis. This enables structured storage and quick access to the prepared dataset.

5. Training the Model and Evaluating Accuracy

def train_model(data, labels):
    df = pd.DataFrame(data)
    X = np.asarray(df[["ChampionId", "Allies", "Enemies"]])
    Y = np.asarray(labels)

    X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.2, shuffle=True)

    model = DecisionTreeClassifier(criterion="entropy", max_depth=5, random_state=4)
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)

    accuracy = accuracy_score(y_test, y_pred)
    print(f"Accuracy: {accuracy:.2%}")
    return model

The train_model function uses a Decision Tree Classifier with entropy criterion to predict match outcomes. After training, it evaluates model accuracy to assess prediction performance.

6. Main Execution

if __name__ == "__main__":
    api = LOLAPI("Elroid")
    processor = MatchDataProcessor(api)

    account_id = api.get_encrypted_account_id()
    match_data = api.get_matches(account_id)
    processed_data, labels = processor.process_match_data(match_data)

    csv_handler = CSVHandler()
    csv_handler.save_to_csv("Elroid", processed_data)

    model = train_model(processed_data, labels)

The main execution block initiates the API, processes match data, saves it to a CSV file, and trains the model. This organized flow facilitates data collection, processing, and machine learning in a single run.

Comments

Be the first one to comment!