import pandas as pd
import numpy as np
import os
import ipywidgets as widgets
from traitlets import Unicode, List, Instance, link, HasTraits
from IPython.display import display, clear_output, HTML, Javascript
features = [[2, 2, 2, 0, 0, 2, 1, 2, 2, 2, 2, 2],
[3, 3, 1, 0, 0, 4, 3, 2, 2, 3, 3, 2],
[1, 3, 2, 0, 0, 2, 0, 0, 2, 2, 3, 1],
[4, 1, 4, 4, 0, 0, 2, 0, 1, 2, 1, 0],
[2, 2, 2, 0, 0, 1, 1, 1, 2, 3, 1, 3],
[2, 3, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1],
[0, 2, 0, 0, 0, 1, 1, 0, 2, 2, 3, 1],
[2, 3, 1, 0, 0, 2, 1, 2, 2, 2, 2, 2],
[2, 2, 1, 0, 0, 1, 0, 0, 2, 2, 2, 1],
[2, 3, 2, 1, 0, 0, 2, 0, 2, 1, 2, 3],
[4, 3, 2, 0, 0, 2, 1, 3, 3, 0, 1, 2],
[3, 2, 1, 0, 0, 3, 2, 1, 0, 2, 2, 2],
[4, 2, 2, 0, 0, 2, 2, 0, 2, 2, 2, 2],
[2, 2, 1, 0, 0, 2, 2, 0, 0, 2, 3, 1],
[3, 2, 2, 0, 0, 3, 1, 1, 2, 3, 2, 2],
[2, 2, 2, 0, 0, 2, 2, 1, 2, 2, 2, 2],
[1, 2, 1, 0, 0, 0, 1, 1, 0, 2, 2, 1],
[2, 2, 2, 0, 0, 1, 2, 2, 2, 2, 2, 2],
[2, 2, 3, 1, 0, 2, 2, 1, 1, 1, 1, 3],
[1, 1, 2, 2, 0, 2, 2, 1, 2, 2, 2, 3],
[1, 2, 1, 1, 0, 1, 1, 1, 1, 2, 2, 1],
[3, 1, 4, 2, 1, 0, 2, 0, 2, 1, 1, 0],
[1, 3, 1, 0, 0, 1, 1, 0, 2, 2, 2, 1],
[3, 2, 3, 3, 1, 0, 2, 0, 1, 1, 2, 0],
[2, 2, 2, 0, 1, 2, 2, 1, 2, 2, 1, 2],
[2, 3, 2, 1, 0, 0, 1, 0, 2, 2, 2, 1],
[4, 2, 2, 0, 0, 1, 2, 2, 2, 2, 2, 2],
[3, 2, 2, 1, 0, 1, 2, 2, 1, 2, 3, 2],
[2, 2, 2, 0, 0, 2, 1, 0, 1, 2, 2, 1],
[2, 2, 1, 0, 0, 2, 1, 1, 1, 3, 2, 2],
[2, 3, 1, 1, 0, 0, 0, 0, 1, 2, 2, 1],
[2, 3, 1, 0, 0, 2, 1, 1, 4, 2, 2, 2],
[2, 3, 1, 1, 1, 1, 1, 2, 0, 2, 0, 3],
[2, 3, 1, 0, 0, 2, 1, 1, 1, 1, 2, 1],
[2, 1, 3, 0, 0, 0, 3, 1, 0, 2, 2, 3],
[1, 2, 0, 0, 0, 1, 0, 1, 2, 1, 2, 1],
[2, 3, 1, 0, 0, 1, 2, 1, 2, 1, 2, 2],
[1, 2, 1, 0, 0, 1, 2, 1, 2, 2, 2, 1],
[3, 2, 1, 0, 0, 1, 2, 1, 1, 2, 2, 2],
[2, 2, 2, 2, 0, 1, 0, 1, 2, 2, 1, 3],
[1, 3, 1, 0, 0, 0, 1, 1, 1, 2, 0, 1],
[1, 3, 1, 0, 0, 1, 1, 0, 1, 2, 2, 1],
[4, 2, 2, 0, 0, 2, 1, 4, 2, 2, 2, 2],
[3, 2, 1, 0, 0, 2, 1, 2, 1, 2, 3, 2],
[2, 4, 1, 0, 0, 1, 2, 3, 2, 3, 2, 2],
[1, 3, 1, 0, 0, 0, 0, 0, 0, 2, 2, 1],
[1, 2, 0, 0, 0, 1, 1, 1, 2, 2, 3, 1],
[1, 2, 1, 0, 0, 1, 2, 0, 0, 2, 2, 1],
[2, 3, 1, 0, 0, 2, 2, 2, 1, 2, 2, 2],
[1, 2, 1, 0, 0, 1, 2, 0, 1, 2, 2, 1],
[2, 2, 1, 1, 0, 1, 2, 0, 2, 1, 2, 1],
[2, 3, 1, 0, 0, 1, 1, 2, 1, 2, 2, 2],
[2, 3, 1, 0, 0, 2, 2, 2, 2, 2, 1, 2],
[2, 2, 3, 1, 0, 2, 1, 1, 1, 2, 1, 3],
[1, 3, 1, 1, 0, 2, 2, 0, 1, 2, 1, 1],
[2, 1, 2, 2, 0, 1, 1, 0, 2, 1, 1, 3],
[2, 3, 1, 0, 0, 2, 2, 1, 2, 1, 2, 2],
[4, 1, 4, 4, 1, 0, 1, 2, 1, 1, 1, 0],
[4, 2, 4, 4, 1, 0, 0, 1, 1, 1, 0, 0],
[2, 3, 1, 0, 0, 1, 1, 2, 0, 1, 3, 1],
[1, 1, 1, 1, 0, 1, 1, 0, 1, 2, 1, 1],
[3, 2, 1, 0, 0, 1, 1, 1, 3, 3, 2, 2],
[4, 3, 1, 0, 0, 2, 1, 4, 2, 2, 3, 2],
[2, 1, 1, 0, 0, 1, 1, 1, 2, 1, 2, 1],
[2, 4, 1, 0, 0, 1, 0, 0, 2, 1, 1, 1],
[3, 2, 2, 0, 0, 2, 3, 3, 2, 1, 2, 2],
[2, 2, 2, 2, 0, 0, 2, 0, 2, 2, 2, 3],
[1, 2, 2, 0, 1, 2, 2, 1, 2, 3, 1, 3],
[2, 1, 2, 2, 1, 0, 1, 1, 2, 2, 2, 3],
[2, 3, 2, 1, 1, 1, 2, 1, 0, 2, 3, 1],
[3, 2, 2, 0, 0, 2, 2, 2, 2, 2, 3, 2],
[2, 2, 1, 1, 0, 2, 1, 1, 2, 2, 2, 2],
[2, 4, 1, 0, 0, 2, 1, 0, 0, 2, 1, 1],
[2, 2, 1, 0, 0, 1, 0, 1, 2, 2, 2, 1],
[2, 2, 2, 2, 0, 2, 2, 1, 2, 1, 0, 3],
[2, 2, 1, 0, 0, 2, 2, 2, 3, 3, 3, 2],
[2, 3, 1, 0, 0, 0, 2, 0, 2, 1, 3, 1],
[4, 2, 3, 3, 0, 1, 3, 0, 1, 2, 2, 0],
[1, 2, 1, 0, 0, 2, 0, 1, 1, 2, 2, 1],
[1, 3, 2, 0, 0, 0, 2, 0, 2, 1, 2, 1],
[2, 2, 2, 1, 0, 0, 2, 0, 0, 0, 2, 3],
[1, 1, 1, 0, 0, 1, 0, 0, 1, 2, 2, 1],
[2, 3, 2, 0, 0, 2, 2, 1, 1, 2, 0, 3],
[0, 3, 1, 0, 0, 2, 2, 1, 1, 2, 1, 1],
[2, 2, 1, 0, 0, 1, 0, 1, 2, 1, 0, 3],
[2, 3, 0, 0, 1, 0, 2, 1, 1, 2, 2, 1]]
feature_names = ['Body', 'Sweetness', 'Smoky',
'Medicinal', 'Tobacco', 'Honey',
'Spicy', 'Winey', 'Nutty',
'Malty', 'Fruity', 'cluster']
brand_names = ['Aberfeldy',
'Aberlour',
'AnCnoc',
'Ardbeg',
'Ardmore',
'ArranIsleOf',
'Auchentoshan',
'Auchroisk',
'Aultmore',
'Balblair',
'Balmenach',
'Belvenie',
'BenNevis',
'Benriach',
'Benrinnes',
'Benromach',
'Bladnoch',
'BlairAthol',
'Bowmore',
'Bruichladdich',
'Bunnahabhain',
'Caol Ila',
'Cardhu',
'Clynelish',
'Craigallechie',
'Craigganmore',
'Dailuaine',
'Dalmore',
'Dalwhinnie',
'Deanston',
'Dufftown',
'Edradour',
'GlenDeveronMacduff',
'GlenElgin',
'GlenGarioch',
'GlenGrant',
'GlenKeith',
'GlenMoray',
'GlenOrd',
'GlenScotia',
'GlenSpey',
'Glenallachie',
'Glendronach',
'Glendullan',
'Glenfarclas',
'Glenfiddich',
'Glengoyne',
'Glenkinchie',
'Glenlivet',
'Glenlossie',
'Glenmorangie',
'Glenrothes',
'Glenturret',
'Highland Park',
'Inchgower',
'Isle of Jura',
'Knochando',
'Lagavulin',
'Laphroig',
'Linkwood',
'Loch Lomond',
'Longmorn',
'Macallan',
'Mannochmore',
'Miltonduff',
'Mortlach',
'Oban',
'OldFettercairn',
'OldPulteney',
'RoyalBrackla',
'RoyalLochnagar',
'Scapa',
'Speyburn',
'Speyside',
'Springbank',
'Strathisla',
'Strathmill',
'Talisker',
'Tamdhu',
'Tamnavulin',
'Teaninich',
'Tobermory',
'Tomatin',
'Tomintoul',
'Tormore',
'Tullibardine']
features_df = pd.DataFrame(features, columns=feature_names, index=brand_names)
features_df = features_df.drop('cluster', axis=1)
norm = (features_df ** 2).sum(axis=1).apply('sqrt')
normed_df = features_df.divide(norm, axis=0)
sim_df = normed_df.dot(normed_df.T)
def radar(df, ax=None):
# calculate evenly-spaced axis angles
num_vars = len(df.columns)
theta = 2*np.pi * np.linspace(0, 1-1./num_vars, num_vars)
# rotate theta such that the first axis is at the top
theta += np.pi/2
if not ax:
fig = plt.figure(figsize=(4, 4))
ax = fig.add_subplot(1,1,1, projection='polar')
else:
ax.clear()
for d, color in zip(df.itertuples(), sns.color_palette()):
ax.plot(theta, d[1:], color=color, alpha=0.7)
ax.fill(theta, d[1:], facecolor=color, alpha=0.5)
ax.set_xticklabels(df.columns)
legend = ax.legend(df.index, loc=(0.9, .95))
return ax
def get_similar(name, n, top=True):
a = sim_df[name].sort_values(ascending=False)
a.name = 'Similarity'
df = pd.DataFrame(a) #.join(features_df).iloc[start:end]
return df.head(n) if top else df.tail(n)
def on_pick_scotch(Scotch):
name = Scotch
# Get top 6 similar whiskeys, and remove this one
top_df = get_similar(name, 6).iloc[1:]
# Get bottom 5 similar whiskeys
df = top_df
# Make table index a set of links that the radar widget will watch
df.index = ['''<a class="scotch" href="#" data-factors_keys='["{}","{}"]'>{}</a>'''.format(name, i, i) for i in df.index]
tmpl = f'''<p>If you like {name} you might want to try these five brands. Click one to see how its taste profile compares.</p>'''
prompt_w.value = tmpl
table.value = df.to_html(escape=False)
lines.x = features_df.loc[Scotch].index.values
lines.y = features_df.loc[Scotch].values
prompt_w = widgets.HTML(value='Aberfeldy')
display(prompt_w)
HTML(value='Aberfeldy')
table = widgets.HTML(
value="Hello <b>World</b>"
)
display(table)
HTML(value='Hello <b>World</b>')
from bqplot import (OrdinalScale, LinearScale, Bars, Lines,
Figure, Axis, ColorScale, ColorAxis, CATEGORY10)
x_ord = OrdinalScale()
y_sc = LinearScale()
lines = Lines(x=features_df.loc['Aberfeldy'].index.values,
y=features_df.loc['Aberfeldy'].values, scales={'x': x_ord, 'y': y_sc},
fill='bottom', fill_colors=['#aaaaff'], fill_opacities=[0.4],
stroke_width=3)
ax_x = Axis(scale=x_ord, tick_rotate=45, tick_style={'font-size': 20})
ax_y = Axis(scale=y_sc, tick_format='0.2f', orientation='vertical')
Figure(marks=[lines], axes=[ax_x, ax_y], animation_duration=500)
Figure(animation_duration=500, axes=[Axis(scale=OrdinalScale(), tick_rotate=45, tick_style={'font-size': 20}),…
picker_w = widgets.interact(on_pick_scotch, Scotch=list(sim_df.index))
interactive(children=(Dropdown(description='Scotch', options=('Aberfeldy', 'Aberlour', 'AnCnoc', 'Ardbeg', 'Ar…
Powered by data from https://www.strath.ac.uk and inspired by analysis from http://blog.revolutionanalytics.com/2013/12/k-means-clustering-86-single-malt-scotch-whiskies.html. This dashboard originated as a Jupyter Notebook.