
WHAT IS LEAGUE OF LEGENDS?
League of Legends is a team-based strategy game where two teams of five powerful champions face off to destroy the other’s base. Choose from over 150 champions to make epic plays, secure kills, and take down towers as you battle your way to victory.
DESTROY THE BASE
The Nexus is the heart of both teams’ bases. Destroy the enemy’s Nexus first to win the game.
THE MAP
Blue and Red team fight on the same map every game. Blue side is on the bottom-left corner, and Red side is on the upper-right corner. The map is symmetrical to its center. It is composed of 3 lanes (toplane, midlane, botlane) and jungle between them. Players pick position in accordance with the role they have decided in the champion select, and has to move from their respective Nexus to the correct position. Each time a player dies or decides to back, he spawns at his respective Nexus.
CLEAR THE PATH
Your team needs to clear at least one lane to get to the enemy Nexus. Blocking your path are defense structures called turrets and inhibitors. Each lane has three turrets and one inhibitor, and each Nexus is guarded by two turrets.
TAKE ON THE JUNGLE
In between the lanes is the jungle, where neutral monsters and jungle plants reside. The two most important monsters are Baron Nashor, the Heralds and the Drakes. Killing these units grants unique buffs for your team and can also turn the tide of the game.
CHOOSE THE LANE
There are five positions that make up the recommended team comp for the game. Each lane lends itself to certain kinds of champions and roles—try them all or lock in to the lane that calls you.
POWER THE CHAMP
Champions get stronger by earning experience to level up and gold to buy more powerful items as the game progresses. Staying on top of these two factors is crucial to overpowering the enemy team and destroying their base.
UNLOCK ABILITIES
Champions have five core abilities, two special spells, and up to seven items at a time. Figuring out the optimal ability order, summoner spells, and item build for your champion will help you succeed as a team.
DATASET
This dataset contains the first 10min. stats of approx. 10k ranked games (SOLO QUEUE) from a high ELO (DIAMOND I to MASTER). Players have roughly the same level.
Each game is unique. The gameId can help you to fetch more attributes from the Riot API.
There are 19 features per team (38 in total) collected after 10min in-game. This includes kills, deaths, gold, experience, level…
The column blueWins is the target value. A value of 1 means the blue team has won. 0 otherwise.
There is no missing value.
In this notebook, we will try to:
- determine which features are relevant to win the game and which are not
- predict the winning percentage of the game based on the first 10 minutes of the game
import pandas as pd
pd.set_option('display.max_columns', 500)
from scipy.stats import ttest_1samp
from matplotlib import pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier, AdaBoostClassifier, ExtraTreesClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC
from xgboost import XGBClassifier
from lightgbm import LGBMClassifier
from catboost import CatBoostClassifier
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
import time
import warnings
warnings.filterwarnings('ignore')
Prepare data¶
df = pd.read_csv('high_diamond_ranked_10min.csv')
df.head()
| gameId | blueWins | blueWardsPlaced | blueWardsDestroyed | blueFirstBlood | blueKills | blueDeaths | blueAssists | blueEliteMonsters | blueDragons | blueHeralds | blueTowersDestroyed | blueTotalGold | blueAvgLevel | blueTotalExperience | blueTotalMinionsKilled | blueTotalJungleMinionsKilled | blueGoldDiff | blueExperienceDiff | blueCSPerMin | blueGoldPerMin | redWardsPlaced | redWardsDestroyed | redFirstBlood | redKills | redDeaths | redAssists | redEliteMonsters | redDragons | redHeralds | redTowersDestroyed | redTotalGold | redAvgLevel | redTotalExperience | redTotalMinionsKilled | redTotalJungleMinionsKilled | redGoldDiff | redExperienceDiff | redCSPerMin | redGoldPerMin | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 4519157822 | 0 | 28 | 2 | 1 | 9 | 6 | 11 | 0 | 0 | 0 | 0 | 17210 | 6.6 | 17039 | 195 | 36 | 643 | -8 | 19.5 | 1721.0 | 15 | 6 | 0 | 6 | 9 | 8 | 0 | 0 | 0 | 0 | 16567 | 6.8 | 17047 | 197 | 55 | -643 | 8 | 19.7 | 1656.7 |
| 1 | 4523371949 | 0 | 12 | 1 | 0 | 5 | 5 | 5 | 0 | 0 | 0 | 0 | 14712 | 6.6 | 16265 | 174 | 43 | -2908 | -1173 | 17.4 | 1471.2 | 12 | 1 | 1 | 5 | 5 | 2 | 2 | 1 | 1 | 1 | 17620 | 6.8 | 17438 | 240 | 52 | 2908 | 1173 | 24.0 | 1762.0 |
| 2 | 4521474530 | 0 | 15 | 0 | 0 | 7 | 11 | 4 | 1 | 1 | 0 | 0 | 16113 | 6.4 | 16221 | 186 | 46 | -1172 | -1033 | 18.6 | 1611.3 | 15 | 3 | 1 | 11 | 7 | 14 | 0 | 0 | 0 | 0 | 17285 | 6.8 | 17254 | 203 | 28 | 1172 | 1033 | 20.3 | 1728.5 |
| 3 | 4524384067 | 0 | 43 | 1 | 0 | 4 | 5 | 5 | 1 | 0 | 1 | 0 | 15157 | 7.0 | 17954 | 201 | 55 | -1321 | -7 | 20.1 | 1515.7 | 15 | 2 | 1 | 5 | 4 | 10 | 0 | 0 | 0 | 0 | 16478 | 7.0 | 17961 | 235 | 47 | 1321 | 7 | 23.5 | 1647.8 |
| 4 | 4436033771 | 0 | 75 | 4 | 0 | 6 | 6 | 6 | 0 | 0 | 0 | 0 | 16400 | 7.0 | 18543 | 210 | 57 | -1004 | 230 | 21.0 | 1640.0 | 17 | 2 | 1 | 6 | 6 | 7 | 1 | 1 | 0 | 0 | 17404 | 7.0 | 18313 | 225 | 67 | 1004 | -230 | 22.5 | 1740.4 |
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 9879 entries, 0 to 9878 Data columns (total 40 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 gameId 9879 non-null int64 1 blueWins 9879 non-null int64 2 blueWardsPlaced 9879 non-null int64 3 blueWardsDestroyed 9879 non-null int64 4 blueFirstBlood 9879 non-null int64 5 blueKills 9879 non-null int64 6 blueDeaths 9879 non-null int64 7 blueAssists 9879 non-null int64 8 blueEliteMonsters 9879 non-null int64 9 blueDragons 9879 non-null int64 10 blueHeralds 9879 non-null int64 11 blueTowersDestroyed 9879 non-null int64 12 blueTotalGold 9879 non-null int64 13 blueAvgLevel 9879 non-null float64 14 blueTotalExperience 9879 non-null int64 15 blueTotalMinionsKilled 9879 non-null int64 16 blueTotalJungleMinionsKilled 9879 non-null int64 17 blueGoldDiff 9879 non-null int64 18 blueExperienceDiff 9879 non-null int64 19 blueCSPerMin 9879 non-null float64 20 blueGoldPerMin 9879 non-null float64 21 redWardsPlaced 9879 non-null int64 22 redWardsDestroyed 9879 non-null int64 23 redFirstBlood 9879 non-null int64 24 redKills 9879 non-null int64 25 redDeaths 9879 non-null int64 26 redAssists 9879 non-null int64 27 redEliteMonsters 9879 non-null int64 28 redDragons 9879 non-null int64 29 redHeralds 9879 non-null int64 30 redTowersDestroyed 9879 non-null int64 31 redTotalGold 9879 non-null int64 32 redAvgLevel 9879 non-null float64 33 redTotalExperience 9879 non-null int64 34 redTotalMinionsKilled 9879 non-null int64 35 redTotalJungleMinionsKilled 9879 non-null int64 36 redGoldDiff 9879 non-null int64 37 redExperienceDiff 9879 non-null int64 38 redCSPerMin 9879 non-null float64 39 redGoldPerMin 9879 non-null float64 dtypes: float64(6), int64(34) memory usage: 3.0 MB
Note: All columns are numeric, so we can use .describe() to get a quick overview of the data
df.describe()
| gameId | blueWins | blueWardsPlaced | blueWardsDestroyed | blueFirstBlood | blueKills | blueDeaths | blueAssists | blueEliteMonsters | blueDragons | blueHeralds | blueTowersDestroyed | blueTotalGold | blueAvgLevel | blueTotalExperience | blueTotalMinionsKilled | blueTotalJungleMinionsKilled | blueGoldDiff | blueExperienceDiff | blueCSPerMin | blueGoldPerMin | redWardsPlaced | redWardsDestroyed | redFirstBlood | redKills | redDeaths | redAssists | redEliteMonsters | redDragons | redHeralds | redTowersDestroyed | redTotalGold | redAvgLevel | redTotalExperience | redTotalMinionsKilled | redTotalJungleMinionsKilled | redGoldDiff | redExperienceDiff | redCSPerMin | redGoldPerMin | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 9.879000e+03 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 | 9879.000000 |
| mean | 4.500084e+09 | 0.499038 | 22.288288 | 2.824881 | 0.504808 | 6.183925 | 6.137666 | 6.645106 | 0.549954 | 0.361980 | 0.187974 | 0.051422 | 16503.455512 | 6.916004 | 17928.110133 | 216.699565 | 50.509667 | 14.414111 | -33.620306 | 21.669956 | 1650.345551 | 22.367952 | 2.723150 | 0.495192 | 6.137666 | 6.183925 | 6.662112 | 0.573135 | 0.413098 | 0.160036 | 0.043021 | 16489.041401 | 6.925316 | 17961.730438 | 217.349226 | 51.313088 | -14.414111 | 33.620306 | 21.734923 | 1648.904140 |
| std | 2.757328e+07 | 0.500024 | 18.019177 | 2.174998 | 0.500002 | 3.011028 | 2.933818 | 4.064520 | 0.625527 | 0.480597 | 0.390712 | 0.244369 | 1535.446636 | 0.305146 | 1200.523764 | 21.858437 | 9.898282 | 2453.349179 | 1920.370438 | 2.185844 | 153.544664 | 18.457427 | 2.138356 | 0.500002 | 2.933818 | 3.011028 | 4.060612 | 0.626482 | 0.492415 | 0.366658 | 0.216900 | 1490.888406 | 0.305311 | 1198.583912 | 21.911668 | 10.027885 | 2453.349179 | 1920.370438 | 2.191167 | 149.088841 |
| min | 4.295358e+09 | 0.000000 | 5.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 10730.000000 | 4.600000 | 10098.000000 | 90.000000 | 0.000000 | -10830.000000 | -9333.000000 | 9.000000 | 1073.000000 | 6.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 11212.000000 | 4.800000 | 10465.000000 | 107.000000 | 4.000000 | -11467.000000 | -8348.000000 | 10.700000 | 1121.200000 |
| 25% | 4.483301e+09 | 0.000000 | 14.000000 | 1.000000 | 0.000000 | 4.000000 | 4.000000 | 4.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 15415.500000 | 6.800000 | 17168.000000 | 202.000000 | 44.000000 | -1585.500000 | -1290.500000 | 20.200000 | 1541.550000 | 14.000000 | 1.000000 | 0.000000 | 4.000000 | 4.000000 | 4.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 15427.500000 | 6.800000 | 17209.500000 | 203.000000 | 44.000000 | -1596.000000 | -1212.000000 | 20.300000 | 1542.750000 |
| 50% | 4.510920e+09 | 0.000000 | 16.000000 | 3.000000 | 1.000000 | 6.000000 | 6.000000 | 6.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 16398.000000 | 7.000000 | 17951.000000 | 218.000000 | 50.000000 | 14.000000 | -28.000000 | 21.800000 | 1639.800000 | 16.000000 | 2.000000 | 0.000000 | 6.000000 | 6.000000 | 6.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 16378.000000 | 7.000000 | 17974.000000 | 218.000000 | 51.000000 | -14.000000 | 28.000000 | 21.800000 | 1637.800000 |
| 75% | 4.521733e+09 | 1.000000 | 20.000000 | 4.000000 | 1.000000 | 8.000000 | 8.000000 | 9.000000 | 1.000000 | 1.000000 | 0.000000 | 0.000000 | 17459.000000 | 7.200000 | 18724.000000 | 232.000000 | 56.000000 | 1596.000000 | 1212.000000 | 23.200000 | 1745.900000 | 20.000000 | 4.000000 | 1.000000 | 8.000000 | 8.000000 | 9.000000 | 1.000000 | 1.000000 | 0.000000 | 0.000000 | 17418.500000 | 7.200000 | 18764.500000 | 233.000000 | 57.000000 | 1585.500000 | 1290.500000 | 23.300000 | 1741.850000 |
| max | 4.527991e+09 | 1.000000 | 250.000000 | 27.000000 | 1.000000 | 22.000000 | 22.000000 | 29.000000 | 2.000000 | 1.000000 | 1.000000 | 4.000000 | 23701.000000 | 8.000000 | 22224.000000 | 283.000000 | 92.000000 | 11467.000000 | 8348.000000 | 28.300000 | 2370.100000 | 276.000000 | 24.000000 | 1.000000 | 22.000000 | 22.000000 | 28.000000 | 2.000000 | 1.000000 | 1.000000 | 2.000000 | 22732.000000 | 8.200000 | 22269.000000 | 289.000000 | 92.000000 | 10830.000000 | 9333.000000 | 28.900000 | 2273.200000 |
print('NaN values: ', df.isna().sum().sum())
NaN values: 0
Note: Data don't have any missing values
df.drop_duplicates(inplace=True)
Clean data¶
df['gameId'].nunique() == len(df)
True
Note: There are no duplicate games, so the gameId is valid. We can drop it.
df.drop('gameId', axis=1, inplace=True)
As domain knowledge, we know that the game first blood is just for only one team. So blueFirstBlood is the opposite of redFirstBlood. We must check if there are any inconsistencies.
sum(df['blueFirstBlood'] == df['redFirstBlood']) # validate that there are no games where both teams got first blood
0
blueGoldDiff and redGoldDiff are must be the opposite of each other. We must check if there are any inconsistencies.
sum(df['blueGoldDiff'] != -df['redGoldDiff']) # validate that there are no games where the gold difference is not the same for both teams
0
blueExperienceDiff and redExperienceDiff are must be the opposite of each other. We must check if there are any inconsistencies.
sum(df['blueExperienceDiff'] != -df['redExperienceDiff']) # validate that there are no games where the experience difference is not the same for both teams
0
Hyporthesis test: Is there a significant win rate difference between the blue team and the red team?¶
sns.countplot(x='blueWins', data=df)
plt.xlabel('Team Wins')
plt.ylabel('Count')
plt.xticks([0, 1], ['Red', 'Blue'])
for p in plt.gca().patches:
height = p.get_height()
percentage = f'{100 * height / len(df):.2f}%'
plt.gca().annotate(percentage, (p.get_x() + p.get_width() / 2, height), ha='center', va='bottom')
plt.title('Team Win Percentage at Origin data')
plt.show()
H0: Porportion of blue team win rate = Porportion of red team win rate = 0.5
H1: Porportion of blue team win rate != Porportion of red team win rate != 0.5
We will use One-sample proportion tests
mu = 0.5
alpha = 0.05
_, p_value = ttest_1samp(df['blueWins'], mu, alternative='two-sided')
print('p-value: ', p_value)
p-value: 0.8484114428598137
if p_value < alpha:
print('Reject H0')
else:
print('Fail to reject H0')
Fail to reject H0
Note: Because p_value is greater than 0.05, we fail to reject the null hypothesis. So there is no significant win rate difference between the blue team and the red team. In other words, the blue team and the red team have the same win rate 50%.
Note 2: So that we move red team data to new rows and change suffixes of columns from _blue and _red to _friendly and _enemy. The result is double rows of data that blue team and red team are treated equally.
Manipulate data¶
new_df = {}
for col in df.columns:
if col.startswith('blue'):
new_df['friendly' + col[len('blue'):]] = []
elif col.startswith('red'):
new_df['enemy' + col[len('red'):]] = []
for index, row in df.iterrows():
for team in ['blue', 'red']:
if team == 'red':
new_df['friendlyWins'].append(1 - row['blueWins'])
for col in df.columns[1:]:
if col.startswith('red'):
new_df['friendly' + col[len('red'):]].append(row[col])
else:
new_df['enemy' + col[len('blue'):]].append(row[col])
else:
new_df['friendlyWins'].append(row['blueWins'])
for col in df.columns[1:]:
if col.startswith('blue'):
new_df['friendly' + col[len('blue'):]].append(row[col])
else:
new_df['enemy' + col[len('red'):]].append(row[col])
new_df = pd.DataFrame(new_df)
new_df.head()
| friendlyWins | friendlyWardsPlaced | friendlyWardsDestroyed | friendlyFirstBlood | friendlyKills | friendlyDeaths | friendlyAssists | friendlyEliteMonsters | friendlyDragons | friendlyHeralds | friendlyTowersDestroyed | friendlyTotalGold | friendlyAvgLevel | friendlyTotalExperience | friendlyTotalMinionsKilled | friendlyTotalJungleMinionsKilled | friendlyGoldDiff | friendlyExperienceDiff | friendlyCSPerMin | friendlyGoldPerMin | enemyWardsPlaced | enemyWardsDestroyed | enemyFirstBlood | enemyKills | enemyDeaths | enemyAssists | enemyEliteMonsters | enemyDragons | enemyHeralds | enemyTowersDestroyed | enemyTotalGold | enemyAvgLevel | enemyTotalExperience | enemyTotalMinionsKilled | enemyTotalJungleMinionsKilled | enemyGoldDiff | enemyExperienceDiff | enemyCSPerMin | enemyGoldPerMin | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.0 | 28.0 | 2.0 | 1.0 | 9.0 | 6.0 | 11.0 | 0.0 | 0.0 | 0.0 | 0.0 | 17210.0 | 6.6 | 17039.0 | 195.0 | 36.0 | 643.0 | -8.0 | 19.5 | 1721.0 | 15.0 | 6.0 | 0.0 | 6.0 | 9.0 | 8.0 | 0.0 | 0.0 | 0.0 | 0.0 | 16567.0 | 6.8 | 17047.0 | 197.0 | 55.0 | -643.0 | 8.0 | 19.7 | 1656.7 |
| 1 | 1.0 | 15.0 | 6.0 | 0.0 | 6.0 | 9.0 | 8.0 | 0.0 | 0.0 | 0.0 | 0.0 | 16567.0 | 6.8 | 17047.0 | 197.0 | 55.0 | -643.0 | 8.0 | 19.7 | 1656.7 | 28.0 | 2.0 | 1.0 | 9.0 | 6.0 | 11.0 | 0.0 | 0.0 | 0.0 | 0.0 | 17210.0 | 6.6 | 17039.0 | 195.0 | 36.0 | 643.0 | -8.0 | 19.5 | 1721.0 |
| 2 | 0.0 | 12.0 | 1.0 | 0.0 | 5.0 | 5.0 | 5.0 | 0.0 | 0.0 | 0.0 | 0.0 | 14712.0 | 6.6 | 16265.0 | 174.0 | 43.0 | -2908.0 | -1173.0 | 17.4 | 1471.2 | 12.0 | 1.0 | 1.0 | 5.0 | 5.0 | 2.0 | 2.0 | 1.0 | 1.0 | 1.0 | 17620.0 | 6.8 | 17438.0 | 240.0 | 52.0 | 2908.0 | 1173.0 | 24.0 | 1762.0 |
| 3 | 1.0 | 12.0 | 1.0 | 1.0 | 5.0 | 5.0 | 2.0 | 2.0 | 1.0 | 1.0 | 1.0 | 17620.0 | 6.8 | 17438.0 | 240.0 | 52.0 | 2908.0 | 1173.0 | 24.0 | 1762.0 | 12.0 | 1.0 | 0.0 | 5.0 | 5.0 | 5.0 | 0.0 | 0.0 | 0.0 | 0.0 | 14712.0 | 6.6 | 16265.0 | 174.0 | 43.0 | -2908.0 | -1173.0 | 17.4 | 1471.2 |
| 4 | 0.0 | 15.0 | 0.0 | 0.0 | 7.0 | 11.0 | 4.0 | 1.0 | 1.0 | 0.0 | 0.0 | 16113.0 | 6.4 | 16221.0 | 186.0 | 46.0 | -1172.0 | -1033.0 | 18.6 | 1611.3 | 15.0 | 3.0 | 1.0 | 11.0 | 7.0 | 14.0 | 0.0 | 0.0 | 0.0 | 0.0 | 17285.0 | 6.8 | 17254.0 | 203.0 | 28.0 | 1172.0 | 1033.0 | 20.3 | 1728.5 |
Create KDA features
friendly_kda = []
enemy_kda = []
for index, row in new_df.iterrows():
if row['friendlyDeaths'] == 0:
friendly_kda.append(row['friendlyKills'] + row['friendlyAssists'])
else:
friendly_kda.append((row['friendlyKills'] + row['friendlyAssists']) / row['friendlyDeaths'])
if row['enemyDeaths'] == 0:
enemy_kda.append(row['enemyKills'] + row['enemyAssists'])
else:
enemy_kda.append((row['enemyKills'] + row['enemyAssists']) / row['enemyDeaths'])
new_df['friendlyKDA'] = friendly_kda
new_df['enemyKDA'] = enemy_kda
Create difference features between friendly and enemy columns
new_df['diffWardsPlaced'] = new_df['friendlyWardsPlaced'] - new_df['enemyWardsPlaced']
new_df['diffWardsDestroyed'] = new_df['friendlyWardsDestroyed'] - new_df['enemyWardsDestroyed']
new_df['firstBlood'] = new_df['friendlyFirstBlood']
new_df['diffKills'] = new_df['friendlyKills'] - new_df['enemyKills']
new_df['diffDeaths'] = new_df['friendlyDeaths'] - new_df['enemyDeaths']
new_df['diffAssists'] = new_df['friendlyAssists'] - new_df['enemyAssists']
new_df['diffEliteMonsters'] = new_df['friendlyEliteMonsters'] - new_df['enemyEliteMonsters']
new_df['diffDragons'] = new_df['friendlyDragons'] - new_df['enemyDragons']
new_df['diffHeralds'] = new_df['friendlyHeralds'] - new_df['enemyHeralds']
new_df['diffTowersDestroyed'] = new_df['friendlyTowersDestroyed'] - new_df['enemyTowersDestroyed']
new_df['diffGold'] = new_df['friendlyGoldDiff']
new_df['diffAvgLevel'] = new_df['friendlyAvgLevel'] - new_df['enemyAvgLevel']
new_df['diffExperience'] = new_df['friendlyExperienceDiff']
new_df['diffMinionsKilled'] = new_df['friendlyTotalMinionsKilled'] - new_df['enemyTotalMinionsKilled']
new_df['diffJungleMinionsKilled'] = new_df['friendlyTotalJungleMinionsKilled'] - new_df['enemyTotalJungleMinionsKilled']
new_df['diffCSPerMin'] = new_df['friendlyCSPerMin'] - new_df['enemyCSPerMin']
new_df['diffGoldPerMin'] = new_df['friendlyGoldPerMin'] - new_df['enemyGoldPerMin']
new_df['diffKDA'] = new_df['friendlyKDA'] - new_df['enemyKDA']
Drop unnecessary columns
new_df.drop([
'friendlyFirstBlood', 'friendlyKills', 'friendlyDeaths', 'friendlyAssists', 'friendlyEliteMonsters',
'friendlyDragons', 'friendlyHeralds', 'friendlyTowersDestroyed', 'friendlyGoldDiff', 'friendlyAvgLevel',
'friendlyExperienceDiff', 'friendlyTotalMinionsKilled', 'friendlyTotalJungleMinionsKilled', 'friendlyCSPerMin',
'friendlyGoldPerMin', 'enemyFirstBlood', 'enemyKills', 'enemyDeaths', 'enemyAssists', 'enemyEliteMonsters',
'enemyDragons', 'enemyHeralds', 'enemyTowersDestroyed', 'enemyGoldDiff', 'enemyAvgLevel', 'enemyExperienceDiff',
'enemyTotalMinionsKilled', 'enemyTotalJungleMinionsKilled', 'enemyCSPerMin', 'enemyGoldPerMin', 'friendlyWardsPlaced',
'enemyWardsPlaced', 'friendlyWardsDestroyed', 'enemyWardsDestroyed', 'friendlyTotalGold', 'friendlyTotalExperience',
'enemyTotalGold', 'enemyTotalExperience', 'enemyTotalGold', 'enemyTotalExperience', 'friendlyKDA', 'enemyKDA'
], axis=1, inplace=True)
Train, test split¶
Before go further, we must split the data into train and test sets to avoid data leakage.
df_train, df_test = train_test_split(new_df, test_size=0.2, random_state=42, stratify=new_df['friendlyWins'])
df_train.describe()
| friendlyWins | diffWardsPlaced | diffWardsDestroyed | firstBlood | diffKills | diffDeaths | diffAssists | diffEliteMonsters | diffDragons | diffHeralds | diffTowersDestroyed | diffGold | diffAvgLevel | diffExperience | diffMinionsKilled | diffJungleMinionsKilled | diffCSPerMin | diffGoldPerMin | diffKDA | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 15806.000000 | 15806.000000 | 15806.000000 | 15806.000000 | 15806.00000 | 15806.00000 | 15806.000000 | 15806.000000 | 15806.000000 | 15806.000000 | 15806.000000 | 15806.000000 | 15806.000000 | 15806.000000 | 15806.000000 | 15806.000000 | 15806.000000 | 15806.000000 | 15806.000000 |
| mean | 0.500000 | 0.104454 | 0.005441 | 0.500949 | -0.00601 | 0.00601 | -0.000759 | -0.000822 | -0.003480 | 0.002657 | -0.000063 | -4.932621 | -0.002683 | -8.644376 | -0.061116 | -0.037770 | -0.006112 | -0.493262 | -0.014567 |
| std | 0.500016 | 26.038485 | 2.845906 | 0.500015 | 4.16984 | 4.16984 | 5.748455 | 1.068516 | 0.880698 | 0.589473 | 0.321437 | 2441.729603 | 0.476593 | 1915.058145 | 31.039593 | 14.314846 | 3.103959 | 244.172960 | 5.131358 |
| min | 0.000000 | -254.000000 | -22.000000 | 0.000000 | -16.00000 | -17.00000 | -22.000000 | -2.000000 | -1.000000 | -1.000000 | -3.000000 | -10830.000000 | -2.600000 | -9333.000000 | -127.000000 | -72.000000 | -12.700000 | -1083.000000 | -39.823529 |
| 25% | 0.000000 | -4.000000 | -1.000000 | 0.000000 | -3.00000 | -3.00000 | -4.000000 | -1.000000 | -1.000000 | 0.000000 | 0.000000 | -1573.750000 | -0.400000 | -1263.750000 | -20.000000 | -9.000000 | -2.000000 | -157.375000 | -2.168290 |
| 50% | 0.500000 | 0.000000 | 0.000000 | 1.000000 | 0.00000 | 0.00000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 3.500000 | 0.000000 | -5.500000 | 0.000000 | 0.000000 | 0.000000 | 0.350000 | 0.000000 |
| 75% | 1.000000 | 4.000000 | 1.000000 | 1.000000 | 3.00000 | 3.00000 | 4.000000 | 1.000000 | 1.000000 | 0.000000 | 0.000000 | 1577.000000 | 0.400000 | 1240.000000 | 20.000000 | 9.000000 | 2.000000 | 157.700000 | 2.166667 |
| max | 1.000000 | 260.000000 | 23.000000 | 1.000000 | 17.00000 | 16.00000 | 25.000000 | 2.000000 | 1.000000 | 1.000000 | 3.000000 | 10830.000000 | 2.600000 | 9333.000000 | 127.000000 | 72.000000 | 12.700000 | 1083.000000 | 35.833333 |
EDA¶
Correlation¶
plt.figure(figsize=(12, 10))
sns.heatmap(df_train.corr(), cmap='coolwarm', annot=True, fmt='.2f')
plt.show()
Mean of stats by win/loss¶
df_train.groupby('friendlyWins').mean()
| diffWardsPlaced | diffWardsDestroyed | firstBlood | diffKills | diffDeaths | diffAssists | diffEliteMonsters | diffDragons | diffHeralds | diffTowersDestroyed | diffGold | diffAvgLevel | diffExperience | diffMinionsKilled | diffJungleMinionsKilled | diffCSPerMin | diffGoldPerMin | diffKDA | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| friendlyWins | ||||||||||||||||||
| 0.0 | -0.273187 | -0.215235 | 0.400987 | -1.995571 | 1.995571 | -2.178287 | -0.281665 | -0.212578 | -0.069088 | -0.049222 | -1245.481716 | -0.217715 | -941.380741 | -9.550677 | -2.436417 | -0.955068 | -124.548172 | -2.116901 |
| 1.0 | 0.482095 | 0.226117 | 0.600911 | 1.983551 | -1.983551 | 2.176768 | 0.280020 | 0.205618 | 0.074402 | 0.049095 | 1235.616475 | 0.212350 | 924.091990 | 9.428445 | 2.360876 | 0.942844 | 123.561647 | 2.087767 |
Note: In genaral, the team with the higher stats like wards placed, wards destroyed, KDA, gold, experience, Drake, Herald, towers destroyed, etc. will win the game.
Wards placed¶
df_train.groupby('diffWardsPlaced')['friendlyWins'].agg(['mean', 'count'])
| mean | count | |
|---|---|---|
| diffWardsPlaced | ||
| -254.0 | 1.0 | 1 |
| -234.0 | 0.0 | 1 |
| -218.0 | 0.0 | 1 |
| -200.0 | 0.0 | 1 |
| -195.0 | 1.0 | 1 |
| ... | ... | ... |
| 200.0 | 1.0 | 1 |
| 218.0 | 1.0 | 1 |
| 234.0 | 1.0 | 1 |
| 254.0 | 0.0 | 1 |
| 260.0 | 1.0 | 1 |
316 rows × 2 columns
sns.histplot(x='diffWardsPlaced', hue='friendlyWins', data=df_train, bins=50, kde=False)
plt.show()
Elite monsters¶
df_train.groupby('diffEliteMonsters')['friendlyWins'].agg(['mean', 'count'])
| mean | count | |
|---|---|---|
| diffEliteMonsters | ||
| -2.0 | 0.269398 | 1147 |
| -1.0 | 0.368231 | 4432 |
| 0.0 | 0.500000 | 4670 |
| 1.0 | 0.634174 | 4401 |
| 2.0 | 0.723183 | 1156 |
- Team got -2 elite monsters at 10 minutes has 28.03% chance to win the game.
- Team got -1 elite monsters at 10 minutes has 36.82% chance to win the game.
- Team got 0 elite monsters at 10 minutes has 50.00% chance to win the game.
- Team got 1 elite monsters at 10 minutes has 62.98% chance to win the game.
- Team got 2 elite monsters at 10 minutes has 72.16% chance to win the game.
First blood¶
df_train.groupby('firstBlood')['friendlyWins'].mean()
firstBlood 0.0 0.399848 1.0 0.599773 Name: friendlyWins, dtype: float64
- Team got first blood has 60.00% chance to win the game.
KDA¶
df_train.groupby('friendlyWins')['diffKDA'].mean()
friendlyWins 0.0 -2.116901 1.0 2.087767 Name: diffKDA, dtype: float64
min_diffKDA = int(df_train['diffKDA'].min())
max_diffKDA = int(df_train['diffKDA'].max())
step = 10
for i in range(min_diffKDA, max_diffKDA, step):
winning_rate = df_train[(df_train['diffKDA'] >= i) & (df_train['diffKDA'] < i + step)]['friendlyWins'].mean()
print(f'with diffKDA between {i} and {i + step}: winning rate is {winning_rate*100:.2f} %')
with diffKDA between -39 and -29: winning rate is 0.00 % with diffKDA between -29 and -19: winning rate is 1.30 % with diffKDA between -19 and -9: winning rate is 10.49 % with diffKDA between -9 and 1: winning rate is 36.81 % with diffKDA between 1 and 11: winning rate is 73.44 % with diffKDA between 11 and 21: winning rate is 92.21 % with diffKDA between 21 and 31: winning rate is 98.18 % with diffKDA between 31 and 41: winning rate is 100.00 %
diff Towers Destroyed¶
gb_diffTowersDestroyed = df_train.groupby('diffTowersDestroyed')['friendlyWins'].mean()
for idx in gb_diffTowersDestroyed.index:
print(f'with diffTowersDestroyed = {idx}: winning rate is {gb_diffTowersDestroyed[idx]*100:.2f} %')
with diffTowersDestroyed = -3.0: winning rate is 0.00 % with diffTowersDestroyed = -2.0: winning rate is 12.20 % with diffTowersDestroyed = -1.0: winning rate is 25.25 % with diffTowersDestroyed = 0.0: winning rate is 49.92 % with diffTowersDestroyed = 1.0: winning rate is 76.66 % with diffTowersDestroyed = 2.0: winning rate is 87.50 % with diffTowersDestroyed = 3.0: winning rate is 100.00 %
df_train.groupby(['friendlyWins', 'firstBlood'])['diffTowersDestroyed'].mean()
friendlyWins firstBlood
0.0 0.0 -0.069497
1.0 -0.018933
1.0 0.0 0.016487
1.0 0.070752
Name: diffTowersDestroyed, dtype: float64
- At lose, the team got first blood can reduce the difference towers destroyed from -0.069 to -0.019
- At win, the team didn't get first blood can loss the difference towers destroyed from 0.071 to 0.016
diff Gold¶
min_diffGold = int(df_train['diffGold'].min())
max_diffGold = int(df_train['diffGold'].max())
step = 2000
for i in range(min_diffGold, max_diffGold, step):
winning_rate = df_train[(df_train['diffGold'] >= i) & (df_train['diffGold'] < i + step)]['friendlyWins'].mean()
print(f'with diffGold between {i} and {i + step}: winning rate is {winning_rate*100:.2f} %')
with diffGold between -10830 and -8830: winning rate is 0.00 % with diffGold between -8830 and -6830: winning rate is 0.00 % with diffGold between -6830 and -4830: winning rate is 3.43 % with diffGold between -4830 and -2830: winning rate is 12.37 % with diffGold between -2830 and -830: winning rate is 26.96 % with diffGold between -830 and 1170: winning rate is 52.54 % with diffGold between 1170 and 3170: winning rate is 76.11 % with diffGold between 3170 and 5170: winning rate is 90.34 % with diffGold between 5170 and 7170: winning rate is 98.43 % with diffGold between 7170 and 9170: winning rate is 100.00 % with diffGold between 9170 and 11170: winning rate is 100.00 %
diff experience¶
min_diffEXP = int(df_train['diffExperience'].min())
max_diffEXP = int(df_train['diffExperience'].max())
step = 2000
for i in range(min_diffEXP, max_diffEXP, step):
winning_rate = df_train[(df_train['diffExperience'] >= i) & (df_train['diffExperience'] < i + step)]['friendlyWins'].mean()
print(f'with diffExperience between {i} and {i + step}: winning rate is {winning_rate*100:.2f} %')
with diffExperience between -9333 and -7333: winning rate is 0.00 % with diffExperience between -7333 and -5333: winning rate is 1.79 % with diffExperience between -5333 and -3333: winning rate is 6.75 % with diffExperience between -3333 and -1333: winning rate is 19.60 % with diffExperience between -1333 and 667: winning rate is 44.98 % with diffExperience between 667 and 2667: winning rate is 73.21 % with diffExperience between 2667 and 4667: winning rate is 90.85 % with diffExperience between 4667 and 6667: winning rate is 97.22 % with diffExperience between 6667 and 8667: winning rate is 100.00 % with diffExperience between 8667 and 10667: winning rate is 100.00 %
sns.scatterplot(data=df_train[df_train['friendlyWins'] == 1], x='diffGold', y='diffExperience', color='#61C0BF')
plt.axvline(x=0, color='black', linestyle='--')
plt.axhline(y=0, color='black', linestyle='--')
plt.title('Gold and Experience difference for winning team')
plt.show()
sns.scatterplot(data=df_train[df_train['friendlyWins'] == 0], x='diffGold', y='diffExperience', color='#F38181')
plt.axvline(x=0, color='black', linestyle='--')
plt.axhline(y=0, color='black', linestyle='--')
plt.title('Gold and Experience difference for losing team')
plt.show()
Predict model¶
X = df_train.copy()
X.drop(
[
'diffWardsPlaced', 'diffWardsDestroyed', 'diffHeralds', 'diffTowersDestroyed', 'diffJungleMinionsKilled'
], axis=1, inplace=True)
y = X.pop('friendlyWins')
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
class LazyClassifier:
def __init__(self, random_state=42):
self.models = {
'Logistic Regression': LogisticRegression(),
'Random Forest': RandomForestClassifier(random_state=random_state),
'Gradient Boosting': GradientBoostingClassifier(random_state=random_state),
'AdaBoost': AdaBoostClassifier(random_state=random_state),
'Extra Trees': ExtraTreesClassifier(random_state=random_state),
'Decision Tree': DecisionTreeClassifier(random_state=random_state),
'SVM': SVC(),
'XGBoost': XGBClassifier(random_state=random_state),
'LightGBM': LGBMClassifier(random_state=random_state),
'CatBoost': CatBoostClassifier(verbose=False, random_seed=random_state)
}
self.fit_times = []
self.predict_times = []
self.train_scores = []
self.val_scores = []
def fit(self, X_train, y_train, X_val, y_val):
for name, model in self.models.items():
print(f'Fitting {name}...')
t1_fitting = time.time()
model.fit(X_train, y_train)
t2_fitting = time.time()
self.fit_times.append(t2_fitting - t1_fitting)
train_score = accuracy_score(y_train, model.predict(X_train))
self.train_scores.append(train_score)
print(f'Predicting {name}...')
t1_predicting = time.time()
y_pred = model.predict(X_val)
t2_predicting = time.time()
self.predict_times.append(t2_predicting - t1_predicting)
val_score = accuracy_score(y_val, y_pred)
self.val_scores.append(val_score)
print(f'{name} done after {t2_predicting - t1_fitting:.2f}s', end='\n\n')
def summary(self):
results = pd.DataFrame({
'Model': list(self.models.keys()),
'Train Score': self.train_scores,
'Val Score': self.val_scores,
'Fit Time': self.fit_times,
'Predict Time': self.predict_times
})
return results.sort_values(['Val Score', 'Train Score'], ascending=False)
Baseline model¶
baseline_model = LazyClassifier()
baseline_model.fit(X_train, y_train, X_val, y_val)
Fitting Logistic Regression... Predicting Logistic Regression... Logistic Regression done after 0.09s Fitting Random Forest... Predicting Random Forest... Random Forest done after 3.87s Fitting Gradient Boosting... Predicting Gradient Boosting... Gradient Boosting done after 3.21s Fitting AdaBoost... Predicting AdaBoost... AdaBoost done after 0.80s Fitting Extra Trees... Predicting Extra Trees... Extra Trees done after 2.06s Fitting Decision Tree... Predicting Decision Tree... Decision Tree done after 0.19s Fitting SVM... Predicting SVM... SVM done after 14.76s Fitting XGBoost... Predicting XGBoost... XGBoost done after 0.14s Fitting LightGBM... [LightGBM] [Info] Number of positive: 6322, number of negative: 6322 [LightGBM] [Info] Auto-choosing col-wise multi-threading, the overhead of testing was 0.000470 seconds. You can set `force_col_wise=true` to remove the overhead. [LightGBM] [Info] Total Bins 1621 [LightGBM] [Info] Number of data points in the train set: 12644, number of used features: 13 [LightGBM] [Info] [binary:BoostFromScore]: pavg=0.500000 -> initscore=0.000000 Predicting LightGBM... LightGBM done after 0.17s Fitting CatBoost... Predicting CatBoost... CatBoost done after 7.75s
baseline_model.summary()
| Model | Train Score | Val Score | Fit Time | Predict Time | |
|---|---|---|---|---|---|
| 3 | AdaBoost | 0.730228 | 0.744782 | 0.723273 | 0.019336 |
| 6 | SVM | 0.722082 | 0.741935 | 4.549563 | 2.104097 |
| 0 | Logistic Regression | 0.726906 | 0.741619 | 0.083477 | 0.001002 |
| 9 | CatBoost | 0.772619 | 0.741303 | 7.736666 | 0.003433 |
| 2 | Gradient Boosting | 0.739718 | 0.740354 | 3.188040 | 0.005680 |
| 8 | LightGBM | 0.778472 | 0.736243 | 0.154515 | 0.003527 |
| 1 | Random Forest | 0.999921 | 0.725490 | 3.632452 | 0.053254 |
| 4 | Extra Trees | 1.000000 | 0.722011 | 1.718927 | 0.077894 |
| 7 | XGBoost | 0.832094 | 0.717900 | 0.127292 | 0.004000 |
| 5 | Decision Tree | 1.000000 | 0.641999 | 0.178621 | 0.002045 |
Standard scale¶
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_val_scaled = scaler.transform(X_val)
scaled_model = LazyClassifier()
scaled_model.fit(X_train_scaled, y_train, X_val_scaled, y_val)
Fitting Logistic Regression... Predicting Logistic Regression... Logistic Regression done after 0.03s Fitting Random Forest... Predicting Random Forest... Random Forest done after 3.89s Fitting Gradient Boosting... Predicting Gradient Boosting... Gradient Boosting done after 3.26s Fitting AdaBoost... Predicting AdaBoost... AdaBoost done after 0.84s Fitting Extra Trees... Predicting Extra Trees... Extra Trees done after 2.04s Fitting Decision Tree... Predicting Decision Tree... Decision Tree done after 0.19s Fitting SVM... Predicting SVM... SVM done after 15.32s Fitting XGBoost... Predicting XGBoost... XGBoost done after 0.10s Fitting LightGBM... [LightGBM] [Info] Number of positive: 6322, number of negative: 6322 [LightGBM] [Info] Auto-choosing col-wise multi-threading, the overhead of testing was 0.000445 seconds. You can set `force_col_wise=true` to remove the overhead. [LightGBM] [Info] Total Bins 1629 [LightGBM] [Info] Number of data points in the train set: 12644, number of used features: 13 [LightGBM] [Info] [binary:BoostFromScore]: pavg=0.500000 -> initscore=0.000000 Predicting LightGBM... LightGBM done after 0.10s Fitting CatBoost... Predicting CatBoost... CatBoost done after 7.83s
scaled_model.summary()
| Model | Train Score | Val Score | Fit Time | Predict Time | |
|---|---|---|---|---|---|
| 0 | Logistic Regression | 0.729279 | 0.746363 | 0.027555 | 0.001007 |
| 3 | AdaBoost | 0.730228 | 0.744782 | 0.758977 | 0.018542 |
| 6 | SVM | 0.728725 | 0.743833 | 4.475697 | 2.198871 |
| 9 | CatBoost | 0.772619 | 0.741303 | 7.819383 | 0.007479 |
| 2 | Gradient Boosting | 0.739718 | 0.740354 | 3.242692 | 0.005013 |
| 8 | LightGBM | 0.776099 | 0.734029 | 0.084430 | 0.003232 |
| 1 | Random Forest | 0.999921 | 0.723909 | 3.653182 | 0.054327 |
| 4 | Extra Trees | 1.000000 | 0.722011 | 1.710580 | 0.076102 |
| 7 | XGBoost | 0.832094 | 0.717900 | 0.093278 | 0.003071 |
| 5 | Decision Tree | 1.000000 | 0.640734 | 0.181613 | 0.000997 |
Polynomial features¶
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2, include_bias=False)
X_poly_train = poly.fit_transform(X_train)
X_poly_val = poly.transform(X_val)
X_poly_train_scaled = scaler.fit_transform(X_poly_train)
X_poly_val_scaled = scaler.transform(X_poly_val)
poly_model = LazyClassifier()
poly_model.fit(X_poly_train, y_train, X_poly_val, y_val)
Fitting Logistic Regression... Predicting Logistic Regression... Logistic Regression done after 0.28s Fitting Random Forest... Predicting Random Forest... Random Forest done after 14.31s Fitting Gradient Boosting... Predicting Gradient Boosting... Gradient Boosting done after 35.44s Fitting AdaBoost... Predicting AdaBoost... AdaBoost done after 6.48s Fitting Extra Trees... Predicting Extra Trees... Extra Trees done after 4.10s Fitting Decision Tree... Predicting Decision Tree... Decision Tree done after 1.97s Fitting SVM... Predicting SVM... SVM done after 48.73s Fitting XGBoost... Predicting XGBoost... XGBoost done after 0.54s Fitting LightGBM... [LightGBM] [Info] Number of positive: 6322, number of negative: 6322 [LightGBM] [Info] Auto-choosing col-wise multi-threading, the overhead of testing was 0.004349 seconds. You can set `force_col_wise=true` to remove the overhead. [LightGBM] [Info] Total Bins 18436 [LightGBM] [Info] Number of data points in the train set: 12644, number of used features: 104 [LightGBM] [Info] [binary:BoostFromScore]: pavg=0.500000 -> initscore=0.000000 Predicting LightGBM... LightGBM done after 0.34s Fitting CatBoost... Predicting CatBoost... CatBoost done after 14.50s
poly_model.summary()
| Model | Train Score | Val Score | Fit Time | Predict Time | |
|---|---|---|---|---|---|
| 9 | CatBoost | 0.792075 | 0.743201 | 14.125572 | 0.078137 |
| 0 | Logistic Regression | 0.721686 | 0.743201 | 0.275979 | 0.001008 |
| 3 | AdaBoost | 0.728883 | 0.741935 | 6.192745 | 0.062771 |
| 2 | Gradient Boosting | 0.744622 | 0.736559 | 35.380134 | 0.006046 |
| 8 | LightGBM | 0.810978 | 0.728969 | 0.326339 | 0.004001 |
| 1 | Random Forest | 0.999921 | 0.725174 | 14.059252 | 0.057003 |
| 4 | Extra Trees | 1.000000 | 0.719481 | 3.799300 | 0.069373 |
| 7 | XGBoost | 0.901851 | 0.700506 | 0.527928 | 0.003000 |
| 5 | Decision Tree | 1.000000 | 0.641682 | 1.964353 | 0.002012 |
| 6 | SVM | 0.507039 | 0.488931 | 22.344151 | 5.245456 |
Early stopping¶
cat_model = CatBoostClassifier(loss_function='Logloss', task_type='GPU', random_state=42)
cat_model.fit(X_train, y_train, verbose=False, eval_set=[(X_val, y_val)], early_stopping_rounds=300)
y_pred = cat_model.predict(X_val)
print('Acurracy: ', accuracy_score(y_val, y_pred))
Acurracy: 0.7441492726122707
print(classification_report(y_val, y_pred))
precision recall f1-score support
0.0 0.75 0.73 0.74 1581
1.0 0.74 0.76 0.75 1581
accuracy 0.74 3162
macro avg 0.74 0.74 0.74 3162
weighted avg 0.74 0.74 0.74 3162
Test model¶
X_test = df_test.copy()
X_test.drop(
[
'diffWardsPlaced', 'diffWardsDestroyed', 'diffHeralds', 'diffTowersDestroyed', 'diffJungleMinionsKilled'
], axis=1, inplace=True)
y_test = X_test.pop('friendlyWins')
y_pred = cat_model.predict(X_test)
print(accuracy_score(y_test, y_pred))
0.7386133603238867
print(classification_report(y_test, y_pred))
precision recall f1-score support
0.0 0.74 0.73 0.74 1976
1.0 0.73 0.75 0.74 1976
accuracy 0.74 3952
macro avg 0.74 0.74 0.74 3952
weighted avg 0.74 0.74 0.74 3952
y_prob = cat_model.predict_proba(X_test)[:, 1]
y_prob = [round(prob*100, 2) for prob in y_prob]
pd.DataFrame({
'Actual': y_test,
'Predicted': y_pred,
'Winning chance (%)': y_prob
})
| Actual | Predicted | Winning chance (%) | |
|---|---|---|---|
| 10969 | 1.0 | 1.0 | 65.80 |
| 8740 | 1.0 | 1.0 | 83.80 |
| 16778 | 1.0 | 0.0 | 21.26 |
| 889 | 1.0 | 1.0 | 73.70 |
| 4187 | 1.0 | 1.0 | 93.47 |
| ... | ... | ... | ... |
| 13962 | 0.0 | 0.0 | 17.74 |
| 9818 | 0.0 | 0.0 | 11.54 |
| 3047 | 0.0 | 0.0 | 25.71 |
| 18072 | 1.0 | 0.0 | 42.91 |
| 3030 | 1.0 | 1.0 | 98.09 |
3952 rows × 3 columns