<jupyter_start><jupyter_text>League of Legends NA Challenger Analysis By: <NAME>     OverviewFor my Data Bootcamp project, I analyzed some factors that could correlate to a player's win rate in the popular MOBA game League of Legends. Using the open-source library [Cassiopeia](http://cassiopeia.readthedocs.io/en/latest/index.html) as well as the developer key linked to my League of Legends account, I was able to pull the data of players in the Challenger tier (top 200 players) on the North American server. Since my analysis was performed only on players in NA Challenger, its implications should not be considered broadly with respect to the general playerbase. Additionally, since the data is on ranked statistics for the current season (season 7) and there are various factors (team composition, champion-specific proficiency, team communication, etc.) that can lead to victory/defeat, this analysis is far from conclusive. Nevertheless, this analysis can provide some interesting insights into the highest level of play. Hopefully you have as much fun reading this as I did making this! Analysis and DisclaimersA simple multivariable linear regression analysis will be performed with win rate as the dependent variable. The normality of the distribution will be evaluated, and the Ordinary Least Squares method will be utilized to generate regression results for the test model. Since this is a simple experiment/exercise in Python, this project will not explore heteroskedasticity, specification bias, collinearity, and other concerns. In turn, the results from this project should not be treated as particularly substantial. CassiopeiaCassiopeia is an open source library used to access Riot API. Installing Cassiopeia will also naturally install SQLAlchemy. command:`pip install cassiopeia   `  SetupIn the following steps, I import necessary modules and access the Riot Games API to pull data on the players in NA Challenger.<jupyter_code>import os                                             # to access system environment variables
import pandas as pd                                   # data management
import seaborn as sns                                 # for data visualization
import matplotlib.pyplot as plt                       # for plots
import statsmodels.formula.api as smf                 # for regression output
import datetime as dt                                 # date information
from cassiopeia import riotapi                        # to access Riot Games API
from cassiopeia.type.core.common import LoadPolicy    # to utilize delayed loading policy

# IPython command, puts plots in notebook 
%matplotlib inline
print('Last Updated', dt.date.today())
riotapi.set_region("NA")                              # sets the region to North America (rip Dyrus)
key = os.environ["DEV_KEY"]                           # grabs my API key from my environment variables
riotapi.set_api_key(key)                              # my dev key is specific to my account
riotapi.set_load_policy(LoadPolicy.lazy)              # lazy -> delays loading certain objects for improved time + data usage<jupyter_output><empty_output><jupyter_text>Challenger DataThe players in Challenger tier are retrieved and stored in challenger_league, a 'League' object. Afterwards, the 'Summoner' object of each entry/player is taken and stored in the list called challenger.<jupyter_code>challenger_league = riotapi.get_challenger()
challenger_league
challenger = [entry.summoner for entry in challenger_league]
challenger[0] #summoner.Summoner object of highest rank player in challenger
challenger[0].name<jupyter_output><empty_output><jupyter_text>The method below returns a dictionary of ranked statistics for a player based on the champion played. Using the key [None] returns aggregate ranked statistics of all champions played by the summoner. This aggregate data can be pulled in JSON format.```challenger[0].ranked_stats()```<jupyter_code>challenger[0].ranked_stats()[None] 
challenger[0].ranked_stats()[None].to_json() #returns data in JSON format<jupyter_output><empty_output><jupyter_text>DataFrameThe ranked stats of each player in Challenger for the current season is converted into a series (list-like structure), then appended into a dataframe (matrix-like structure). This dataframe has player data as row entries and ranked statistics by columns.<jupyter_code>df = pd.DataFrame()
for player in challenger:
    stats = pd.read_json(player.ranked_stats()[None].to_json(), typ = 'series')
    df = df.append(stats, ignore_index=True)<jupyter_output><empty_output><jupyter_text>Below is a view of the dataframe for the top 5 players. Several of the fields are left as 0.0 as information for them are not recorded for ranked statistics.<jupyter_code>df.head()
df.shape #200 player entries, 56 field columns
df.columns<jupyter_output><empty_output><jupyter_text>Note: following code not updated, but still correctly conveys that all fields are of datatype float<jupyter_code>df.dtypes #all values are floats<jupyter_output><empty_output><jupyter_text>Data ManipulationSince some of the fields I wanted were not available or were filled with 0.0s, I decided to add in fields to the dataframe for future usage. Below, average assists, average kills, average deaths, win rate, and average gold earned have been included.<jupyter_code>df['averageAssists'] = df['totalAssists']/df['totalSessionsPlayed']            # will overwrite the 'averageAssists' field
df['averageKills'] = df['totalChampionKills']/df['totalSessionsPlayed']
df['averageDeaths'] = df['totalDeathsPerSession']/df['totalSessionsPlayed']
df['winRate'] = df['totalSessionsWon']/df['totalSessionsPlayed']
df['averageGoldEarned'] = df['totalGoldEarned']/df['totalSessionsPlayed']<jupyter_output><empty_output><jupyter_text>As an example, the average assists of the top 5 players from the newly included averageAssists series can be seen below.<jupyter_code>df['averageAssists'].head()<jupyter_output><empty_output><jupyter_text>Win RateAs the dependent variable of my linear regression model, the win rate variable is explored in the following blocks. Below is a plot of the win rates for all 200 players from highest to lowest rank (left to right).<jupyter_code>df['winRate'].plot()<jupyter_output><empty_output><jupyter_text>From the descriptive statistics below, it is interesting to note that the win rates of all players in Challenger are above 50%. This makes sense as the ranked positions of all players are reset at the end of each season, and top players must climb the ranked ladder in order to get into Challenger. Thus, successfully winning at least as many games as losses seems like a necessary, but not sufficient condition to make it into Challenger.<jupyter_code>df['winRate'].describe()<jupyter_output><empty_output><jupyter_text>Before moving onto the test model, the normality of the distribution of the win rates is evaluated. With a normally distribution, the skewness is 0 (neither skewed to the left or right) and the kurtosis is 3 ("tailedness" of the distribution). Since the win rate distribution has a positive skewness and a kurtosis less than 3, the distribution is skewed to the right and has a platykurtic distribution (peak looks flatter). A sample of 200 of the top players in a region is not be expected to be normally distributed, and the skew towards a higher win rate as well as a platykurtic distribution (fewer and less influential outliers) seem to represent this. Nevertheless, a multivariate linear regression will be performed on this dataset despite failing the normality condition.<jupyter_code>df['winRate'].skew() # positive skewness -> skewed to the right (skewed to higher winrate)
df['winRate'].kurt() # kurtosis less than 3 -> platykurtic 
sns.distplot(df['winRate'], bins=50)<jupyter_output>C:\Users\Chris\Anaconda3\lib\site-packages\statsmodels\nonparametric\kdetools.py:20: VisibleDeprecationWarning: using a non-integer number instead of an integer will result in an error in the future
  y = X[:m/2+1] + np.r_[0,X[m/2+1:],0]*1j<jupyter_text>Multivariate Linear Regression Models Test Model 1`WINRATE = B0 + B1(GOLD) + B2(KDA) + B3(TURRETS) + B4(DIFF) + B5(GAMES)`- WINRATE: winRate- B0 : intercept- B1 to B5 : coefficients- GOLD : averageGoldEarned- KDA : kda; kill-death-assist ratio; (average kills + average assists)/average deaths- TURRETS : averageTurretsKilled- DIFF : averageDamageDifferential; (damage dealt - damage taken + total healing)/games played- GAMES : totalSessionsPlayed Expectations- GOLD is expected to have a positive effect on WINRATE; a higher GOLD value expresses that a player should be more influential in an average game (can buy more wards, have more of their build completed, etc.)- KDA is expected to have a positive effect on WINRATE; a higher KDA value should indirectly help a team achieve their goals (greater participation in kills, dying less to the enemy team)- TURRETS is expected to have a positive effect on WINRATE; a higher TURRETS value can be seen as a player being more objective-focused- DIFF is expected to have a positive effect on WINRATE; a higher DIFF value suggests being more useful to a team (dealing more damage than taken and/or providing healing)- GAMES is expected to have a negative effect on WINRATE; a higher GAMES value can lead to a more stable WINRATE (more solidified win rate that tends to be lower)<jupyter_code>df['kda'] = (df['averageAssists'] + df['averageKills'])/df['averageDeaths']
df['averageTurretsKilled'] = df['totalTurretsKilled']/df['totalSessionsPlayed']
df['averageDamageDifferential'] = (df['totalDamageDealt'] - df['totalDamageTaken'] + df['totalHeal'])/df['totalSessionsPlayed']
df.shape #200 players, 63 columns (7 added, 1 overwritten)<jupyter_output><empty_output><jupyter_text>ScatterplotsBelow are the scatterplots of each independent variable against win rate. They should help to visualize any relationships that arise within the data.  Average Gold Earned vs. Win RateBased on the plot below, the win rate range seems to widen as the average gold earned increases. Additionally, the players with the highest win rates tend to have average gold earned between 11,000 to 13,000. It is important to note that games do vary in length (like when SKT steamrolled Royal Club in the [Season 3 World Championship Finals](https://www.reddit.com/r/leagueoflegends/comments/1nrufd/spoiler_royal_club_vs_sk_telecom_t1_postmatch/)), so the average gold earned can also vary.<jupyter_code>fig, ax = plt.subplots()
ax.scatter(df['winRate'], df['averageGoldEarned'])
ax.set_title('Average Gold Earned vs. Win Rate', loc='left', fontsize=14)
ax.set_xlabel('Win Rate')
ax.set_ylabel('Average Gold Earned')<jupyter_output><empty_output><jupyter_text>KDA Ratio vs. Win RateBased on the plot below, the KDAs and win rates of most players seem to be clustered in the bottom left. As the KDA Ratio increases, there seems to be an upward trend in win rate. It should be noted that having a high KDA does not necessarily lead to a greater win rate, as being too aggressive or not focusing on objectives enough may be detrimental to a team's success.<jupyter_code>fig, ax = plt.subplots()
ax.scatter(df['winRate'], df['kda'])
ax.set_title('KDA Ratio vs. Win Rate', loc='left', fontsize=14)
ax.set_xlabel('Win Rate')
ax.set_ylabel('KDA Ratio')<jupyter_output><empty_output><jupyter_text>Average Turrets Killed vs. Win RateBased on the plot below, it does not seem like the average turrets killed correlates with win rate. Players can also contribute to the team's victory without needing to kill a turret personally (such as letting minions finish off a turret after a push or having a laning partner take the turret).<jupyter_code>fig, ax = plt.subplots()
ax.scatter(df['winRate'], df['averageTurretsKilled'])
ax.set_title('Average Turrets Killed vs. Win Rate', loc='left', fontsize=14)
ax.set_xlabel('Win Rate')
ax.set_ylabel('Average Turrets Killed')<jupyter_output><empty_output><jupyter_text>Average Damage Differential vs. Win RateBased on the plot below, most of the data points seem to be clustered within the average damage differential range of 100,000 to 150,000. Aside from a few data points, it seems that the players with the highest win rates tend to be within that range. Players below that range seem to have win rates on the lower end of the spectrum, suggesting that they may not be contributing enough (in team fights or being effective in their particular roles). It is important to note that certain roles are not expected to contribute as much in terms of damage output, so a support main can still have a high WINRATE value despite having a consistently low DIFF.<jupyter_code>fig, ax = plt.subplots()
ax.scatter(df['winRate'], df['averageDamageDifferential'])
ax.set_title('Average Damage Differential vs. Win Rate', loc='left', fontsize=14)
ax.set_xlabel('Win Rate')
ax.set_ylabel('Average Damage Differential')<jupyter_output><empty_output><jupyter_text>Games Played vs. Win RateThis plot expresses that the number of games played is inversely proportional to a player's win rate. As the number of games a player plays increases, that player's respective win rate decreases, approaching the lower limit of 50%. This relationship seems to make sense given the pool of NA Challenger players. With fewer ranked games played, a Challenger player has a greater portion of games played at lower tiers due to climbing the ranked ladder, so that player's win rate would be higher. Additionally, players on win streaks can climb the ladder faster, and can enter into Challenger with a higher than average win rate.<jupyter_code>fig, ax = plt.subplots()
ax.scatter(df['winRate'], df['totalSessionsPlayed'])
ax.set_title('Games Played vs. Win Rate', loc='left', fontsize=14)
ax.set_xlabel('Win Rate')
ax.set_ylabel('Games Played')<jupyter_output><empty_output><jupyter_text>OLS RegressionBased on the test model established before Test Model 1`WINRATE = B0 + B1(GOLD) + B2(KDA) + B3(TURRETS) + B4(DIFF) + B5(GAMES)`regression output using the Ordinary Least Squares method is generated below using the StatsModel module. The "winRate" is passed as the dependent variable while the other variables are passed as independent variables in the "model" argument string.<jupyter_code>model = 'winRate ~ averageGoldEarned + kda + averageTurretsKilled + averageDamageDifferential + totalSessionsPlayed'
results = smf.ols(model, data=df).fit()
results.summary()<jupyter_output><empty_output><jupyter_text>Test Model 1 Analysis Test Model 1:`WINRATE = 0.5016 + 0.000003407(GOLD) + 0.0180(KDA) + 0.0175(TURRETS) - 0.00000005839(DIFF) - 0.00009006(GAMES)`From our regression results, the R-squared statistic of 0.559 expresses that 55.9% of the variability in the data is accounted for in the linear regression model. Adjusting for the number of predictors within the model, the adjusted R-squared statistic is 0.547 or 54.7%. Through these r-squared values, the regression results seem to suggest that the model is somewhat good at explaining the variability in the data. Nevertheless, a couple of the explanatory variables within the model have insignificant t-values and high p-values. A high r-squared statistic and a few significant t-values suggest that the model might be suffering from a classical case of multicollinearity (two or more predictor values are highly correlated). To deal with multicollinearity, variables that are insignificant will be dropped in the second test model.The variables with insignificant t-values and high p-values are GOLD and DIFF(`194 degrees of freedom: |t-values|  alpha of 0.05` | [t-value table](http://www.sjsu.edu/faculty/gerstman/StatPrimer/t-table.pdf) ). In turn, the second test model will only have the following independent variables: KDA, TURRETS, GAMES. Test Model 2:`WINRATE = B0 + B1(KDA) + B2(TURRETS) + B3(GAMES)`<jupyter_code>model2 = 'winRate ~ kda + averageTurretsKilled + totalSessionsPlayed'
results2 = smf.ols(model2, data=df).fit()
results2.summary()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07113022878872888, 'fraction_numerical': 0.011345926064459822, 'mean_word_length': 5.1135670731707314, 'pattern_counts': {'":': 0, '<': 77, '<?xml version=': 0, '>': 80, 'https://': 1, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 7, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '19', 'dir': 'jupyter-structured-clean-dedup', 'id': '172097', 'license': 'MIT', 'n_tokens_mistral': 4593, 'n_tokens_neox': 4291, 'n_words': 1945, 'path': 'UG_S17/Luo-LeagueOfLegends.ipynb', 'repo_name': 'NYUDataBootcamp/Projects', 'size': '16044'}