{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from bokeh.plotting import figure, show, output_notebook"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# Generate some synthetic time series for six different categories\n",
    "cats = list(\"abcdef\")\n",
    "y = np.random.randn(2000)\n",
    "g = np.random.choice(cats, 2000)\n",
    "for i, l in enumerate(cats):\n",
    "    y[g == l] += i // 2\n",
    "df = pd.DataFrame(dict(score=y, group=g))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# Find the quartiles and IQR foor each category\n",
    "groups = df.groupby('group')\n",
    "q1 = groups.quantile(q=0.25)\n",
    "q2 = groups.quantile(q=0.5)\n",
    "q3 = groups.quantile(q=0.75)\n",
    "iqr = q3 - q1\n",
    "upper = q3 + 1.5*iqr\n",
    "lower = q1 - 1.5*iqr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# find the outliers for each category\n",
    "def outliers(group):\n",
    "   cat = group.name\n",
    "   return group[(group.score > upper.loc[cat][0]) | (group.score < lower.loc[cat][0])]['score']\n",
    "out = groups.apply(outliers).dropna()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# Prepare outlier data for plotting, we need coordinate for every outlier.\n",
    "outx = []\n",
    "outy = []\n",
    "for cat in cats:\n",
    "    # only add outliers if they exist\n",
    "    if (not out.loc[cat].empty):\n",
    "        for value in out[cat]:\n",
    "            outx.append(cat)\n",
    "            outy.append(value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# If no outliers, shrink lengths of stems to be no longer than the minimums or maximums\n",
    "qmin = groups.quantile(q=0.00) \n",
    "qmax = groups.quantile(q=1.00)\n",
    "upper.score = [min([x,y]) for (x,y) in zip(list(qmax.iloc[:,0]),upper.score) ]\n",
    "lower.score = [max([x,y]) for (x,y) in zip(list(qmin.iloc[:,0]),lower.score) ]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "output_notebook()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "p = figure(tools=\"previewsave\", background_fill=\"#EFE8E2\", title=\"\", x_range=cats)\n",
    "\n",
    "# stems\n",
    "p.segment(cats, upper.score, cats, q3.score, line_width=2, line_color=\"black\")\n",
    "p.segment(cats, lower.score, cats, q1.score, line_width=2, line_color=\"black\")\n",
    "\n",
    "# boxes\n",
    "p.rect(cats, (q3.score+q2.score)/2, 0.7, q3.score-q2.score,\n",
    "     fill_color=\"#E08E79\", line_width=2, line_color=\"black\")\n",
    "p.rect(cats, (q2.score+q1.score)/2, 0.7, q2.score-q1.score,\n",
    "     fill_color=\"#3B8686\", line_width=2, line_color=\"black\")\n",
    "\n",
    "# whisters (almost-0 height rects simpler than segments)\n",
    "p.rect(cats, lower.score, 0.2, 0.01, line_color=\"black\")\n",
    "p.rect(cats, upper.score, 0.2, 0.01, line_color=\"black\")\n",
    "\n",
    "# outliers\n",
    "p.circle(outx, outy, size=6, color=\"#F38630\", fill_alpha=0.6)\n",
    "\n",
    "p.xgrid.grid_line_color = None\n",
    "p.ygrid.grid_line_color = \"white\"\n",
    "p.ygrid.grid_line_width = 2\n",
    "p.xaxis.major_label_text_font_size=\"12pt\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "show(p)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
