Interactive Smite-Recommender Widget

NOTE: Unfortunately, the widget below will not function on a static HTML page. If you are viewing this in GitHub or nbviewer, the only way to interact with it is to have Jupyter installed on your computer, download the file containing this notebook, and view it through Jupyter on your own computer.

What items go well with/against certain gods? Let's find out!

A regularized logistic regression supplies a rating for each item as it tries to predict which items lead to a win. Using this information, we suggest useful alternatives to the most popular items used.

Try pairing up a guardian and a hunter in ranked, or compare recommendations between enemies in duel vs ranked mode.

Just remember, item use *correlating* with a win does not necessarily mean that the item helped *cause* the win!

In [1]:
import pandas as pd
from IPython.display import display, HTML, clear_output
from seaborn import light_palette
from ipywidgets import interact
import ipywidgets as widgets
import numpy as np
import os.path

def centered(html_string):
    return(HTML('<center>{}</center>'.format(html_string)))

def color_it(s, color = "#bbdcdc"):
    return('background-color: {}'.format(color))

def color_negative_red(val):
    if isinstance(val, float):
        color = '#d25349' if val < 0 else 'black'
    else: color= 'black'
    return('color: {}'.format(color))

def highlight_popular(val, popular_items):
    if val not in popular_items:
        weight = 'bold'
    else: weight = 'normal'
    return('font-weight: {}'.format(weight))

def itemstyle(df, pop_items, relic=False):
    cm = light_palette("#7cbbbb", as_cmap=True)
    output = df.style.background_gradient(cmap=cm)\
    .applymap(highlight_popular, popular_items=pop_items)\
    .applymap(color_negative_red)\
    .format({'-Rating-': "{:.2f}",'Rating': "{:.2f}",'-Win Rate-': '{:.1%}', 'Win Rate': '{:.1%}'})
    if relic == False:
        output.format({'Rating/Cost': "{:.2f}",'Win Rate/Cost': '{:.3}'})
    else: 
        output.format({"Paired": "{:.1%}",'-Paired-': '{:.1%}'})
    return(centered(output.render()))

def recommendationstyle(df, pop_items):
    cm = light_palette("#7cbbbb", as_cmap=True)
    output = df.style.background_gradient(cmap=cm).applymap(color_negative_red)\
    .applymap(highlight_popular, popular_items=pop_items)\
    .format({'Win Rate': '{:.1%}', 'Win Rate/Cost': '{:.3}'})
    return(centered(output.render()))

In [2]:
gods_lookup = pd.DataFrame.from_csv("SMITE_data/SMITE_Gods_All_Lookup.csv", index_col="Name")
god_names = list(gods_lookup.index)

def self_align(s):
    """ Allows for text input to update dropdown widget"""
    if self_text.value.title() in god_names:
        self.value = self_text.value.title()
self_text = widgets.Text(placeholder="Enter a god and hit 'Enter'")
self = widgets.Dropdown(options=god_names, value='Agni', button_style='success')
self_text.on_submit(self_align)
               
def partner_align(s):
    """ Allows for text input to update dropdown widget"""
    if partner_text.value.title() in god_names:
        partner.value = partner_text.value.title()
partner_text = widgets.Text(placeholder="Enter a god and hit 'Enter'")
partner = widgets.Dropdown(options=god_names, value='Agni', button_style='info')
partner_text.on_submit(partner_align)

godcardsHTML='<img src={} style="float:left; border-radius: 40px; border: 8px solid green;" width="380px" height="512px"/>'\
    '<img src={} style="float:right; border-radius: 40px; border: 8px solid {};" width="380px" height="512px"/>'

def update(button):
    """ pulls SmiteRecommender dfs based on widget input, and returns styled pandas dfs of results
        needs SMITE_recommendations folder in the same directory to work.
    """
    clear_output()
    if toggle_gamemode.value == "casual":
        print("No data for casual mode. Please try a different mode")
        return
    selfcard = gods_lookup.loc[self.value, "godCard_URL"]
    partnercard = gods_lookup.loc[partner.value, "godCard_URL"]
    partnercolor = "green" if toggle_friend.value == True else "darkred"
    godcards.value = godcardsHTML.format(selfcard, partnercard, partnercolor)
    
    god_id = gods_lookup.loc[self.value, "id"]
    partner_id = gods_lookup.loc[partner.value, "id"]
    friend = "_friend/" if toggle_friend.value == True else "_foe/"
    path = "SMITE_recommendations/" + toggle_gamemode.value + friend
    try:
        fullpath = path + "{}-{}_info.txt".format(god_id, partner_id)
        if os.path.getsize(fullpath) == 0:
            print("Empty file found. Please try a different pairing.")
            return
        with open(fullpath, "rb") as f:
            info_table = f.read().decode("utf-8")
        print(info_table)
        
    except OSError:
        print("Pairing not allowed, or less than 100 matches.")
    try:
        recommendation = pd.DataFrame.from_csv(path + "{}-{}_recommendation.csv".format(god_id, partner_id), parse_dates=False).fillna(0)
        recommendation.index.name = ''
        recommendation.columns = ["Recommendation", "Win Rate", "Win Rate/Cost"]
        items = pd.DataFrame.from_csv(path + "{}-{}_items.csv".format(god_id, partner_id), index_col= None, parse_dates=False).fillna(0)
        items.columns = ["Most Popular", "-Rating-", "-Win Rate-", "Most Recommended", "Rating", "Win Rate", "Cost Adjusted", "Rating/Cost", "Win Rate/Cost"]
        relics = pd.DataFrame.from_csv(path + "{}-{}_relics.csv".format(god_id, partner_id), index_col= None, parse_dates=False).fillna(0)
        relics.columns = ["Most Popular", "-Rating-", "-Win Rate-", "-Paired-", "Most Recommended", "Rating", "Win Rate", "Paired"]
        relics.loc[1,"-Paired-"]=relics.loc[0,"-Paired-"]
        relics.loc[1,"Paired"]=relics.loc[0,"Paired"]
        pop_items = list(items["Most Popular"].values) + list(relics["Most Popular"].values)
        display(recommendationstyle(recommendation, pop_items))
        display(itemstyle(items, pop_items))
        display(itemstyle(relics, pop_items, relic=True))
    except OSError:
        print("Unable to get recommendation. Please try a different pairing.")

In [3]:
toggle_friend = widgets.ToggleButtons(options={'Friend': True, 'Foe': False},margin = '10px')
toggle_gamemode = widgets.ToggleButtons(options={'Ranked' : "ranked", 'Casual' : "casual", 'Joust' : "joust", "Duel": "duel"}, margin = '10px')
button = widgets.Button(description='Get Recommendation', button_style='warning', pack= 'center', margin= '0 243px 30px 243px')

center  = widgets.Layout(align_self="center",justify_content= 'center', width= "50%")
firstrow = widgets.HBox(children=[toggle_friend, toggle_gamemode], layout=center)
secondrow = widgets.HBox(children=[self, self_text], layout=center)
thirdrow = widgets.HBox(children=[partner, partner_text], layout=center)
button = widgets.Button(description='Get Recommendation', button_style='warning',layout=center)

godcards = widgets.HTML(value=None)

In [4]:
button.on_click(update)
recommender_widget = widgets.VBox(children=[firstrow, secondrow, thirdrow, button], layout=widgets.Layout(margin = "25px"))
display(recommender_widget)
display(godcards)

In [5]:
# found from a stackoverflow answer: http://tinyurl.com/zwl2wtl
HTML('''<script>
code_show=true; 
function code_toggle() {
 if (code_show){
 $('div.input').hide();
 } else {
 $('div.input').show();
 }
 code_show = !code_show
} 
$( document ).ready(code_toggle);
</script>
<center><form action="javascript:code_toggle()"><input type="submit" value="View/Hide code cells."></form><center>''')


Out[5]:

Recommendation Info:

Recommendations are made by replacing the two popular items with the worst rating with the best two new items from the cost adjusted rating. This is an imperfect approach, but does a decent job most of the time.

  • Items are NOT ordered (except boots). Our algorithm doesn't have a way to intelligently tell us when to buy each item.
  • Starter Items are chosen based on popularity.
  • Items are bolded to show that they were not in the original six most popular items.
  • Recommended items were used in at least 5% of matches, Recommended relics in at least 3%.
  • Only paired recommendations with at least 100 matches will be shown.
  • Currently, there is no data for casual mode.
  • The data is more trustworthy for god pairings that are popular (or high tier) in the given game mode. Unpopular gods, in Duel especially, have few pairings with over 100 matches.
  • The more in-game interaction a given pair of gods have (Guardian-Hunter friends, duel mode, etc), the more the results will show deviations from a standard build.
  • Due to a (now fixed) code bug, the paired relic winrate columns are duplicating the data for the most recommended relics, and starter items sometimes show up twice in a recommendation.
  • This data is for Smite games from October and November, 2016, and there doesn't seem to be any data for Camazotz.

Gamemodes:

  • Ranked Conquest (5v5)
  • Casual Conquest (5v5)
  • Ranked Joust (3v3)
  • Ranked Duel (1v1)

Recommendation Details:

  • Cost adjusted rating (rating/cost) is used to help reduce the possibility of luxury items (like Spear of Desolation or Mantle of Discord) being recommended. We don't want to recommend 'win-more' items: items that help an already winning team continue to win, but are not helpful if you are losing. The cost adjusted rating presents the best value per gold spent, as opposed to pure value.
  • We do not simply suggest the 6 items with the highest rating because some items are so ubiquitious (everyone buys them) that they are actually terrible at predicting a winning outcome (which is where our recommendations come from). Many items that do not predict a winning outcome are still fundamentally necessary (like boots!).
    • There is also still the problem of "win-more" items. An item might predict winning quite well simply because a team would only pick that item up if it is likely to win. Item use correlating with a win does not necessarily mean that the item helped cause the win!
  • If a recommendation seems off, often the ratings will tell you the full story. Relic recommendations should be taken with a grain of salt, and are usually quit situational. Sometimes relics have a winrate of 100%, meaning they were used very few times, which could skew their rating.
  • Note that item use correlating with a win/loss does not necessarily mean that the item helped cause the win/loss, and that these recommendations are based only off of end game data.

    • Win-more items have a high rating and win rate, because having them is a good predictor that you won the game. However, being ahead is probably what causes you to buy expensive items; buying expensive items is not in and of itself going to cause you to win (quite the opposite).

    • Starter items are meant to be replaced by more powerful items later in the game. Having a starter item at the end of the game is a good predictor that you lost (meaning it has a negative rating). It likely didn't cause the loss, it's simply a good indicator that you were behind in gold and experience relative to your opponents.

    • As a final example, a relic like Sanctuary or Purification can have a negative rating but high popularity. This could be because getting the relic is necessary in certain matchups (like against hard CC), but you are still likely to lose. In this case, getting the relic correlates with the specific pairing, and the specific pairing correlates with a poor win rate, so the relic correlates with, but is not responsible for, a poor win rate.