Jimin Park
added new structure
ccc2ebe
# app.py
import gradio as gr
import xgboost as xgb
from xgboost import DMatrix
from huggingface_hub import hf_hub_download
from app_training_df_getter import create_app_user_training_df
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from helper import *
from helper import ChampionConverter
import joblib
# Define champion list for dropdowns
CHAMPIONS = [
"Aatrox", "Ahri", "Akali", "Akshan", "Alistar", "Amumu", "Anivia", "Annie", "Aphelios", "Ashe",
"Aurelion Sol", "Azir", "Bard", "Bel'Veth", "Blitzcrank", "Brand", "Braum", "Caitlyn", "Camille",
"Cassiopeia", "Cho'Gath", "Corki", "Darius", "Diana", "Dr. Mundo", "Draven", "Ekko", "Elise",
"Evelynn", "Ezreal", "Fiddlesticks", "Fiora", "Fizz", "Galio", "Gangplank", "Garen", "Gnar",
"Gragas", "Graves", "Gwen", "Hecarim", "Heimerdinger", "Illaoi", "Irelia", "Ivern", "Janna",
"Jarvan IV", "Jax", "Jayce", "Jhin", "Jinx", "Kai'Sa", "Kalista", "Karma", "Karthus", "Kassadin",
"Katarina", "Kayle", "Kayn", "Kennen", "Kha'Zix", "Kindred", "Kled", "Kog'Maw", "KSante", "LeBlanc",
"Lee Sin", "Leona", "Lillia", "Lissandra", "Lucian", "Lulu", "Lux", "Malphite", "Malzahar", "Maokai",
"Master Yi", "Milio", "Miss Fortune", "Mordekaiser", "Morgana", "Naafiri", "Nami", "Nasus", "Nautilus",
"Neeko", "Nidalee", "Nilah", "Nocturne", "Nunu & Willump", "Olaf", "Orianna", "Ornn", "Pantheon",
"Poppy", "Pyke", "Qiyana", "Quinn", "Rakan", "Rammus", "Rek'Sai", "Rell", "Renata Glasc", "Renekton",
"Rengar", "Riven", "Rumble", "Ryze", "Samira", "Sejuani", "Senna", "Seraphine", "Sett", "Shaco",
"Shen", "Shyvana", "Singed", "Sion", "Sivir", "Skarner", "Sona", "Soraka", "Swain", "Sylas",
"Syndra", "Tahm Kench", "Taliyah", "Talon", "Taric", "Teemo", "Thresh", "Tristana", "Trundle",
"Tryndamere", "Twisted Fate", "Twitch", "Udyr", "Urgot", "Varus", "Vayne", "Veigar", "Vel'Koz",
"Vex", "Vi", "Viego", "Viktor", "Vladimir", "Volibear", "Warwick", "Wukong", "Xayah", "Xerath",
"Xin Zhao", "Yasuo", "Yone", "Yorick", "Yuumi", "Zac", "Zed", "Zeri", "Ziggs", "Zilean", "Zoe", "Zyra"
]
try:
label_encoder = joblib.load('util/label_encoder.joblib')
n_classes = len(label_encoder.classes_)
print("Label encoder loaded successfully")
except Exception as e:
print(f"Error loading label encoder: {e}")
label_encoder = None
# Load model
try:
model_path = hf_hub_download(
repo_id="ivwhy/champion-predictor-model",
filename="champion_predictor.json"
)
# Initialize the model with proper parameters
model = xgb.XGBClassifier(
use_label_encoder=False,
objective='multi:softprob',
num_class=n_classes
)
# Load the model
model._Booster = xgb.Booster()
model._Booster.load_model(model_path)
# Set only n_classes_
model.n_classes_ = n_classes
except Exception as e:
print(f"Error loading model: {e}")
model = None
# Initialize champion name encoder
# champion_encoder = LabelEncoder()
# champion_encoder.fit(CHAMPIONS)
#==================================== Functions =================================================
def get_user_training_df(player_opgg_url):
try:
print("========= Inside get_user_training_df(player_opgg_url) ============= \n")
#print("player_opgg_url: ", player_opgg_url, "\n type(player_opgg_url): ", type(player_opgg_url), "\n")
# Add input validation
if not player_opgg_url or not isinstance(player_opgg_url, str):
return "Invalid URL provided"
training_df = create_app_user_training_df(player_opgg_url)
return training_df
except Exception as e:
# Add more detailed error information
import traceback
error_trace = traceback.format_exc()
print(f"Full error trace:\n{error_trace}")
return f"Error getting training data: {str(e)}"
#return f"Error getting training data: {e}"
def show_stats(player_opgg_url):
"""Display player statistics and recent matches"""
'''
to add: playstyle, role_specialization, champion_loyalty_score, most_champ_1, most_champ_2 ,
'''
if not player_opgg_url:
return "Please enter a player link to OPGG", None
try:
training_features = get_user_training_df(player_opgg_url)
print("training_features: ", training_features, "\n")
if isinstance(training_features, str): # Error message
return training_features, None
wins = training_features['result'].sum()
losses = len(training_features) - wins
winrate = f"{(wins / len(training_features)) * 100:.0f}%"
favorite_champions = (
training_features['champion']
.value_counts()
.head(3)
.index.tolist()
)
# print("training_features['playstyle']: \n", training_features['playstyle'])
# print("training_features['role_specialization]: \n", training_features['role_specialization'])
# print("training_features: \n", training_features[champion])
# Extract additional stats
# playstyle = training_features['playstyle'].mode()[0] if 'playstyle' in training_features else 'N/A'
# print("processed playstyle.\n")
# role_specialization = training_features['role_specialization'].mode()[0] if 'role_specialization' in training_features else 'N/A' # Most common role
# print("processed role_specialization.\n")
#champion_loyalty_score = training_features['champion_loyalty_score'].mean().round(2) if 'champion_loyalty_score' in training_features else 'N/A' # Average loyalty
# print("processed champion_loyalty_score.\n")
# Map numeric playstyle to descriptive text
# playstyle_mapping = {
# 0: "Assassin/Carry",
# 1: "Support/Utility",
# 2: "Tank/Initiator",
# 3: "Split-pusher",
# 4: "Aggressive/Fighter",
# 5: "Undefined"
# }
# role_specialization_map = {
# 0: "Pure Specialist",
# 1: "Strong Dual Role",
# 2: "Primary Role with Backups",
# 3: "Role Swapper",
# 4: "True Flex",
# 5: "Undefined"
# }
stats_html = f"""
<div style='padding: 20px; background: #f5f5f5; border-radius: 10px;'>
<h3>Player's Recent Stats</h3>
<p>Wins: {wins} | Losses: {losses}</p>
<p>Winrate: {winrate}</p>
<p>Favorite Champions: {', '.join(favorite_champions)}</p>
</div>
"""
# <p>Playstyle: {playstyle_mapping.get(playstyle, 'N/A')}</p>
# <p>Role Specialization: {role_specialization_map.get(role_specialization, 'N/A')}</p>
# <p>Champion Loyalty Score: {champion_loyalty_score}</p>
return stats_html, None
except Exception as e:
return f"Error processing stats: {e}. ", None
def predict_top_5_champion_w_confidence(player_opgg_url, *champions):
"""Make prediction based on selected champions"""
print("Selected Champions from Dropdowns:", champions)
if not player_opgg_url or None in champions:
return "Please fill in all fields"
try:
if model is None:
return "Model not loaded properly"
if label_encoder is None:
return "Label encoder not loaded properly"
# Get and process the data
training_df = get_user_training_df(player_opgg_url)
if isinstance(training_df, str):
return training_df
training_df = convert_df(training_df)
print("check_datatypes(training_df) BEFORE feature eng: \n", check_datatypes(training_df), "\n")
training_df = apply_feature_engineering(training_df)
print("check_datatypes(training_df) AFTER feature eng: \n", check_datatypes(training_df), "\n")
label_column = training_df['champion']
predict_column = training_df.drop(columns=['champion', 'region'])
# Mapping dropdown selections to the correct columns
champ_columns = [
'team_champ1', 'team_champ2', 'team_champ3', 'team_champ4',
'opp_champ1', 'opp_champ2', 'opp_champ3', 'opp_champ4', 'opp_champ5'
]
champion_converter = ChampionConverter()
# Update predict_column with user-selected champions
for col, champ_name in zip(champ_columns, champions):
champ_num = champion_converter.champion_to_num(champ_name)
predict_column.at[0, col] = champ_num
proba = model.predict_proba(predict_column)
# Get top 5 indices and probabilities
top_5_idx = np.argsort(proba, axis=1)[:, -5:][:, ::-1]
top_5_proba = np.take_along_axis(proba, top_5_idx, axis=1)
# Initialize results DataFrame
results = pd.DataFrame()
champion_converter = ChampionConverter()
# Add true champion - convert numeric label to champion name
true_numbers = label_column
results['True_Champion'] = [champion_converter.num_to_champion(int(num)) for num in true_numbers]
# Process each rank separately
for i in range(5):
# Convert indices to champion names using the champion converter
champions = [champion_converter.num_to_champion(int(label_encoder.classes_[idx])) for idx in top_5_idx[:, i]]
probabilities = top_5_proba[:, i]
# Add to results
results[f'Rank_{i+1}_Champion'] = champions
results[f'Rank_{i+1}_Confidence'] = probabilities.round(4)
try:
def find_champion_rank(row):
true_champ = row['True_Champion']
for i in range(1, 6):
if row[f'Rank_{i}_Champion'] == true_champ:
return f'Rank_{i}'
return 'Not in Top 5'
results['Prediction_Rank'] = results.apply(find_champion_rank, axis=1)
# Select the last row and specific columns
latest_result = results.iloc[-1][["Rank_1_Champion", "Rank_2_Champion", "Rank_3_Champion"]].tolist()
latest_result = results.iloc[-1][["Rank_1_Champion", "Rank_2_Champion", "Rank_3_Champion"]]
clean_output = "\n".join(f"{col}: {val}" for col, val in latest_result.items())
print(clean_output)
return clean_output
except Exception as e:
print(f"Error getting top 5 champions: {e}")
except Exception as e:
import traceback
print(f"Full error trace:\n{traceback.format_exc()}")
return f"Error making prediction: {e}"
# Define your interface
with gr.Blocks() as demo:
gr.Markdown("# League of Legends Champion Prediction")
with gr.Row():
player_opgg_url = gr.Textbox(label="OPGG Player URL")
show_button = gr.Button("Show Player Stats")
with gr.Row():
stats_output = gr.HTML(label="Player Statistics")
recent_matches = gr.HTML(label="Recent Matches")
with gr.Row():
champion_dropdowns = [
gr.Dropdown(choices=CHAMPIONS, label=f"Champion {i+1}")
for i in range(9)
]
with gr.Row():
predict_button = gr.Button("Predict")
prediction_output = gr.Text(label="Prediction")
# Set up event handlers
show_button.click(
fn=show_stats,
inputs=[player_opgg_url],
outputs=[stats_output, recent_matches]
)
predict_button.click(
fn=predict_top_5_champion_w_confidence,
inputs=[player_opgg_url] + champion_dropdowns,
outputs=prediction_output
)
# Optional: Save the champion encoder for future use
#joblib.dump(champion_encoder, 'champion_encoder.joblib')
# Enable queuing
demo.launch(debug=True)
# For local testing
if __name__ == "__main__":
demo.launch()