{ "cells": [ { "cell_type": "markdown", "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": 0, "height": 2, "hidden": false, "locked": false, "row": 0, "width": 12 }, "report_default": { "hidden": false } } } } }, "source": [ "# Got Scotch?" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": null, "height": 2, "hidden": true, "locked": false, "row": null, "width": 2 }, "report_default": { "hidden": true } } } } }, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "import os" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": null, "height": 2, "hidden": true, "locked": false, "row": null, "width": 2 }, "report_default": { "hidden": true } } } } }, "outputs": [], "source": [ "import ipywidgets as widgets\n", "from traitlets import Unicode, List, Instance, link, HasTraits\n", "from IPython.display import display, clear_output, HTML, Javascript" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": null, "height": 2, "hidden": true, "locked": false, "row": null, "width": 2 }, "report_default": {} } } } }, "outputs": [], "source": [ "\n", "\n", "features = [[2, 2, 2, 0, 0, 2, 1, 2, 2, 2, 2, 2],\n", " [3, 3, 1, 0, 0, 4, 3, 2, 2, 3, 3, 2],\n", " [1, 3, 2, 0, 0, 2, 0, 0, 2, 2, 3, 1],\n", " [4, 1, 4, 4, 0, 0, 2, 0, 1, 2, 1, 0],\n", " [2, 2, 2, 0, 0, 1, 1, 1, 2, 3, 1, 3],\n", " [2, 3, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1],\n", " [0, 2, 0, 0, 0, 1, 1, 0, 2, 2, 3, 1],\n", " [2, 3, 1, 0, 0, 2, 1, 2, 2, 2, 2, 2],\n", " [2, 2, 1, 0, 0, 1, 0, 0, 2, 2, 2, 1],\n", " [2, 3, 2, 1, 0, 0, 2, 0, 2, 1, 2, 3],\n", " [4, 3, 2, 0, 0, 2, 1, 3, 3, 0, 1, 2],\n", " [3, 2, 1, 0, 0, 3, 2, 1, 0, 2, 2, 2],\n", " [4, 2, 2, 0, 0, 2, 2, 0, 2, 2, 2, 2],\n", " [2, 2, 1, 0, 0, 2, 2, 0, 0, 2, 3, 1],\n", " [3, 2, 2, 0, 0, 3, 1, 1, 2, 3, 2, 2],\n", " [2, 2, 2, 0, 0, 2, 2, 1, 2, 2, 2, 2],\n", " [1, 2, 1, 0, 0, 0, 1, 1, 0, 2, 2, 1],\n", " [2, 2, 2, 0, 0, 1, 2, 2, 2, 2, 2, 2],\n", " [2, 2, 3, 1, 0, 2, 2, 1, 1, 1, 1, 3],\n", " [1, 1, 2, 2, 0, 2, 2, 1, 2, 2, 2, 3],\n", " [1, 2, 1, 1, 0, 1, 1, 1, 1, 2, 2, 1],\n", " [3, 1, 4, 2, 1, 0, 2, 0, 2, 1, 1, 0],\n", " [1, 3, 1, 0, 0, 1, 1, 0, 2, 2, 2, 1],\n", " [3, 2, 3, 3, 1, 0, 2, 0, 1, 1, 2, 0],\n", " [2, 2, 2, 0, 1, 2, 2, 1, 2, 2, 1, 2],\n", " [2, 3, 2, 1, 0, 0, 1, 0, 2, 2, 2, 1],\n", " [4, 2, 2, 0, 0, 1, 2, 2, 2, 2, 2, 2],\n", " [3, 2, 2, 1, 0, 1, 2, 2, 1, 2, 3, 2],\n", " [2, 2, 2, 0, 0, 2, 1, 0, 1, 2, 2, 1],\n", " [2, 2, 1, 0, 0, 2, 1, 1, 1, 3, 2, 2],\n", " [2, 3, 1, 1, 0, 0, 0, 0, 1, 2, 2, 1],\n", " [2, 3, 1, 0, 0, 2, 1, 1, 4, 2, 2, 2],\n", " [2, 3, 1, 1, 1, 1, 1, 2, 0, 2, 0, 3],\n", " [2, 3, 1, 0, 0, 2, 1, 1, 1, 1, 2, 1],\n", " [2, 1, 3, 0, 0, 0, 3, 1, 0, 2, 2, 3],\n", " [1, 2, 0, 0, 0, 1, 0, 1, 2, 1, 2, 1],\n", " [2, 3, 1, 0, 0, 1, 2, 1, 2, 1, 2, 2],\n", " [1, 2, 1, 0, 0, 1, 2, 1, 2, 2, 2, 1],\n", " [3, 2, 1, 0, 0, 1, 2, 1, 1, 2, 2, 2],\n", " [2, 2, 2, 2, 0, 1, 0, 1, 2, 2, 1, 3],\n", " [1, 3, 1, 0, 0, 0, 1, 1, 1, 2, 0, 1],\n", " [1, 3, 1, 0, 0, 1, 1, 0, 1, 2, 2, 1],\n", " [4, 2, 2, 0, 0, 2, 1, 4, 2, 2, 2, 2],\n", " [3, 2, 1, 0, 0, 2, 1, 2, 1, 2, 3, 2],\n", " [2, 4, 1, 0, 0, 1, 2, 3, 2, 3, 2, 2],\n", " [1, 3, 1, 0, 0, 0, 0, 0, 0, 2, 2, 1],\n", " [1, 2, 0, 0, 0, 1, 1, 1, 2, 2, 3, 1],\n", " [1, 2, 1, 0, 0, 1, 2, 0, 0, 2, 2, 1],\n", " [2, 3, 1, 0, 0, 2, 2, 2, 1, 2, 2, 2],\n", " [1, 2, 1, 0, 0, 1, 2, 0, 1, 2, 2, 1],\n", " [2, 2, 1, 1, 0, 1, 2, 0, 2, 1, 2, 1],\n", " [2, 3, 1, 0, 0, 1, 1, 2, 1, 2, 2, 2],\n", " [2, 3, 1, 0, 0, 2, 2, 2, 2, 2, 1, 2],\n", " [2, 2, 3, 1, 0, 2, 1, 1, 1, 2, 1, 3],\n", " [1, 3, 1, 1, 0, 2, 2, 0, 1, 2, 1, 1],\n", " [2, 1, 2, 2, 0, 1, 1, 0, 2, 1, 1, 3],\n", " [2, 3, 1, 0, 0, 2, 2, 1, 2, 1, 2, 2],\n", " [4, 1, 4, 4, 1, 0, 1, 2, 1, 1, 1, 0],\n", " [4, 2, 4, 4, 1, 0, 0, 1, 1, 1, 0, 0],\n", " [2, 3, 1, 0, 0, 1, 1, 2, 0, 1, 3, 1],\n", " [1, 1, 1, 1, 0, 1, 1, 0, 1, 2, 1, 1],\n", " [3, 2, 1, 0, 0, 1, 1, 1, 3, 3, 2, 2],\n", " [4, 3, 1, 0, 0, 2, 1, 4, 2, 2, 3, 2],\n", " [2, 1, 1, 0, 0, 1, 1, 1, 2, 1, 2, 1],\n", " [2, 4, 1, 0, 0, 1, 0, 0, 2, 1, 1, 1],\n", " [3, 2, 2, 0, 0, 2, 3, 3, 2, 1, 2, 2],\n", " [2, 2, 2, 2, 0, 0, 2, 0, 2, 2, 2, 3],\n", " [1, 2, 2, 0, 1, 2, 2, 1, 2, 3, 1, 3],\n", " [2, 1, 2, 2, 1, 0, 1, 1, 2, 2, 2, 3],\n", " [2, 3, 2, 1, 1, 1, 2, 1, 0, 2, 3, 1],\n", " [3, 2, 2, 0, 0, 2, 2, 2, 2, 2, 3, 2],\n", " [2, 2, 1, 1, 0, 2, 1, 1, 2, 2, 2, 2],\n", " [2, 4, 1, 0, 0, 2, 1, 0, 0, 2, 1, 1],\n", " [2, 2, 1, 0, 0, 1, 0, 1, 2, 2, 2, 1],\n", " [2, 2, 2, 2, 0, 2, 2, 1, 2, 1, 0, 3],\n", " [2, 2, 1, 0, 0, 2, 2, 2, 3, 3, 3, 2],\n", " [2, 3, 1, 0, 0, 0, 2, 0, 2, 1, 3, 1],\n", " [4, 2, 3, 3, 0, 1, 3, 0, 1, 2, 2, 0],\n", " [1, 2, 1, 0, 0, 2, 0, 1, 1, 2, 2, 1],\n", " [1, 3, 2, 0, 0, 0, 2, 0, 2, 1, 2, 1],\n", " [2, 2, 2, 1, 0, 0, 2, 0, 0, 0, 2, 3],\n", " [1, 1, 1, 0, 0, 1, 0, 0, 1, 2, 2, 1],\n", " [2, 3, 2, 0, 0, 2, 2, 1, 1, 2, 0, 3],\n", " [0, 3, 1, 0, 0, 2, 2, 1, 1, 2, 1, 1],\n", " [2, 2, 1, 0, 0, 1, 0, 1, 2, 1, 0, 3],\n", " [2, 3, 0, 0, 1, 0, 2, 1, 1, 2, 2, 1]]\n", "\n", "feature_names = ['Body', 'Sweetness', 'Smoky', \n", " 'Medicinal', 'Tobacco', 'Honey',\n", " 'Spicy', 'Winey', 'Nutty',\n", " 'Malty', 'Fruity', 'cluster']\n", "\n", "brand_names = ['Aberfeldy',\n", " 'Aberlour',\n", " 'AnCnoc',\n", " 'Ardbeg',\n", " 'Ardmore',\n", " 'ArranIsleOf',\n", " 'Auchentoshan',\n", " 'Auchroisk',\n", " 'Aultmore',\n", " 'Balblair',\n", " 'Balmenach',\n", " 'Belvenie',\n", " 'BenNevis',\n", " 'Benriach',\n", " 'Benrinnes',\n", " 'Benromach',\n", " 'Bladnoch',\n", " 'BlairAthol',\n", " 'Bowmore',\n", " 'Bruichladdich',\n", " 'Bunnahabhain',\n", " 'Caol Ila',\n", " 'Cardhu',\n", " 'Clynelish',\n", " 'Craigallechie',\n", " 'Craigganmore',\n", " 'Dailuaine',\n", " 'Dalmore',\n", " 'Dalwhinnie',\n", " 'Deanston',\n", " 'Dufftown',\n", " 'Edradour',\n", " 'GlenDeveronMacduff',\n", " 'GlenElgin',\n", " 'GlenGarioch',\n", " 'GlenGrant',\n", " 'GlenKeith',\n", " 'GlenMoray',\n", " 'GlenOrd',\n", " 'GlenScotia',\n", " 'GlenSpey',\n", " 'Glenallachie',\n", " 'Glendronach',\n", " 'Glendullan',\n", " 'Glenfarclas',\n", " 'Glenfiddich',\n", " 'Glengoyne',\n", " 'Glenkinchie',\n", " 'Glenlivet',\n", " 'Glenlossie',\n", " 'Glenmorangie',\n", " 'Glenrothes',\n", " 'Glenturret',\n", " 'Highland Park',\n", " 'Inchgower',\n", " 'Isle of Jura',\n", " 'Knochando',\n", " 'Lagavulin',\n", " 'Laphroig',\n", " 'Linkwood',\n", " 'Loch Lomond',\n", " 'Longmorn',\n", " 'Macallan',\n", " 'Mannochmore',\n", " 'Miltonduff',\n", " 'Mortlach',\n", " 'Oban',\n", " 'OldFettercairn',\n", " 'OldPulteney',\n", " 'RoyalBrackla',\n", " 'RoyalLochnagar',\n", " 'Scapa',\n", " 'Speyburn',\n", " 'Speyside',\n", " 'Springbank',\n", " 'Strathisla',\n", " 'Strathmill',\n", " 'Talisker',\n", " 'Tamdhu',\n", " 'Tamnavulin',\n", " 'Teaninich',\n", " 'Tobermory',\n", " 'Tomatin',\n", " 'Tomintoul',\n", " 'Tormore',\n", " 'Tullibardine']" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": null, "height": 2, "hidden": true, "locked": false, "row": null, "width": 2 }, "report_default": {} } } } }, "outputs": [], "source": [ "features_df = pd.DataFrame(features, columns=feature_names, index=brand_names)\n", "features_df = features_df.drop('cluster', axis=1)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": null, "height": 2, "hidden": true, "locked": false, "row": null, "width": 2 }, "report_default": {} } } } }, "outputs": [], "source": [ "norm = (features_df ** 2).sum(axis=1).apply('sqrt')\n", "normed_df = features_df.divide(norm, axis=0)\n", "sim_df = normed_df.dot(normed_df.T)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": null, "height": 2, "hidden": true, "locked": false, "row": null, "width": 2 }, "report_default": { "hidden": false } } } } }, "outputs": [], "source": [ "def radar(df, ax=None):\n", " # calculate evenly-spaced axis angles\n", " num_vars = len(df.columns)\n", " theta = 2*np.pi * np.linspace(0, 1-1./num_vars, num_vars)\n", " # rotate theta such that the first axis is at the top\n", " theta += np.pi/2\n", " if not ax:\n", " fig = plt.figure(figsize=(4, 4))\n", "\n", " ax = fig.add_subplot(1,1,1, projection='polar')\n", " else:\n", " ax.clear()\n", " for d, color in zip(df.itertuples(), sns.color_palette()):\n", " ax.plot(theta, d[1:], color=color, alpha=0.7)\n", " ax.fill(theta, d[1:], facecolor=color, alpha=0.5)\n", " ax.set_xticklabels(df.columns)\n", "\n", " legend = ax.legend(df.index, loc=(0.9, .95))\n", " return ax" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": null, "height": 2, "hidden": true, "locked": false, "row": null, "width": 2 }, "report_default": { "hidden": false } } } } }, "outputs": [], "source": [ "def get_similar(name, n, top=True):\n", " a = sim_df[name].sort_values(ascending=False)\n", " a.name = 'Similarity'\n", " df = pd.DataFrame(a) #.join(features_df).iloc[start:end]\n", " return df.head(n) if top else df.tail(n)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": null, "height": 2, "hidden": true, "locked": false, "row": null, "width": 2 }, "report_default": { "hidden": false } } } } }, "outputs": [], "source": [ "def on_pick_scotch(Scotch):\n", " name = Scotch\n", " # Get top 6 similar whiskeys, and remove this one\n", " top_df = get_similar(name, 6).iloc[1:]\n", " # Get bottom 5 similar whiskeys\n", " df = top_df\n", " \n", " # Make table index a set of links that the radar widget will watch\n", " df.index = ['''{}'''.format(name, i, i) for i in df.index]\n", " \n", " tmpl = f'''

If you like {name} you might want to try these five brands. Click one to see how its taste profile compares.

'''\n", " prompt_w.value = tmpl\n", " table.value = df.to_html(escape=False)\n", " lines.x = features_df.loc[Scotch].index.values\n", " lines.y = features_df.loc[Scotch].values" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": 0, "height": 2, "hidden": false, "locked": false, "row": 2, "width": 12 }, "report_default": { "hidden": false } } } } }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "9793f6c5c68647e29bbfa50519370ea2", "version_major": 2, "version_minor": 0 }, "text/plain": [ "HTML(value='Aberfeldy')" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "prompt_w = widgets.HTML(value='Aberfeldy')\n", "display(prompt_w)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": 0, "height": 6, "hidden": false, "locked": false, "row": 7, "width": 4 }, "report_default": {} } } } }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "219ba9be2b984eaf9aa3432da29c2aae", "version_major": 2, "version_minor": 0 }, "text/plain": [ "HTML(value='Hello World')" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "table = widgets.HTML(\n", " value=\"Hello World\"\n", ")\n", "display(table)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": 4, "height": 9, "hidden": false, "locked": false, "row": 4, "width": 8 }, "report_default": { "hidden": false } } } } }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "51195e849bb740e9a6cfe5edcb244c96", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(animation_duration=500, axes=[Axis(scale=OrdinalScale(), tick_rotate=45, tick_style={'font-size': 20}),…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bqplot import (OrdinalScale, LinearScale, Bars, Lines,\n", " Figure, Axis, ColorScale, ColorAxis, CATEGORY10)\n", "x_ord = OrdinalScale()\n", "y_sc = LinearScale()\n", "\n", "lines = Lines(x=features_df.loc['Aberfeldy'].index.values,\n", " y=features_df.loc['Aberfeldy'].values, scales={'x': x_ord, 'y': y_sc},\n", " fill='bottom', fill_colors=['#aaaaff'], fill_opacities=[0.4],\n", " stroke_width=3)\n", "ax_x = Axis(scale=x_ord, tick_rotate=45, tick_style={'font-size': 20})\n", "ax_y = Axis(scale=y_sc, tick_format='0.2f', orientation='vertical')\n", "\n", "Figure(marks=[lines], axes=[ax_x, ax_y], animation_duration=500)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": 0, "height": 3, "hidden": false, "locked": false, "row": 4, "width": 4 }, "report_default": { "hidden": false } } } } }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "c3cce0f1be5540c999a2975031ae887e", "version_major": 2, "version_minor": 0 }, "text/plain": [ "interactive(children=(Dropdown(description='Scotch', options=('Aberfeldy', 'Aberlour', 'AnCnoc', 'Ardbeg', 'Ar…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "picker_w = widgets.interact(on_pick_scotch, Scotch=list(sim_df.index))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "extensions": { "jupyter_dashboards": { "version": 1, "views": { "grid_default": { "col": 0, "height": 2, "hidden": false, "locked": false, "row": 13, "width": 12 }, "report_default": { "hidden": false } } } } }, "source": [ "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." ] } ], "metadata": { "anaconda-cloud": {}, "celltoolbar": "Edit Metadata", "extensions": { "jupyter_dashboards": { "activeView": "grid_default", "version": 1, "views": { "grid_default": { "cellMargin": 10, "defaultCellHeight": 50, "maxColumns": 12, "name": "grid", "type": "grid" }, "report_default": { "name": "report", "type": "report" } } } }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.9" }, "voila": { "template": "gridstack" } }, "nbformat": 4, "nbformat_minor": 4 }