#!/usr/bin/env python # coding: utf-8 # # Got Scotch? # In[13]: import pandas as pd import numpy as np import os # In[14]: import ipywidgets as widgets from traitlets import Unicode, List, Instance, link, HasTraits from IPython.display import display, clear_output, HTML, Javascript # In[15]: 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'] # In[16]: features_df = pd.DataFrame(features, columns=feature_names, index=brand_names) features_df = features_df.drop('cluster', axis=1) # In[17]: 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) # In[18]: 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 # In[19]: 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) # In[20]: 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 = ['''{}'''.format(name, i, i) for i in df.index] tmpl = f'''
If you like {name} you might want to try these five brands. Click one to see how its taste profile compares.
''' 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 # In[21]: prompt_w = widgets.HTML(value='Aberfeldy') display(prompt_w) # In[22]: table = widgets.HTML( value="Hello World" ) display(table) # In[23]: 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) # In[24]: picker_w = widgets.interact(on_pick_scotch, Scotch=list(sim_df.index)) # 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.