{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Creating a waterfall chart using Bokeh\n",
"\n",
"This is the notebook associated with the article at [Pbpython.com](http://pbpython.com/bokeh-bullet-waterfall.html)"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from bokeh.plotting import figure, show\n",
"from bokeh.io import output_notebook\n",
"from bokeh.models import ColumnDataSource, LabelSet\n",
"from bokeh.models.formatters import NumeralTickFormatter\n",
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"
\n",
"
\n",
"
Loading BokehJS ...\n",
"
"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/javascript": [
"\n",
"(function(root) {\n",
" function now() {\n",
" return new Date();\n",
" }\n",
"\n",
" var force = true;\n",
"\n",
" if (typeof (root._bokeh_onload_callbacks) === \"undefined\" || force === true) {\n",
" root._bokeh_onload_callbacks = [];\n",
" root._bokeh_is_loading = undefined;\n",
" }\n",
"\n",
" var JS_MIME_TYPE = 'application/javascript';\n",
" var HTML_MIME_TYPE = 'text/html';\n",
" var EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n",
" var CLASS_NAME = 'output_bokeh rendered_html';\n",
"\n",
" /**\n",
" * Render data to the DOM node\n",
" */\n",
" function render(props, node) {\n",
" var script = document.createElement(\"script\");\n",
" node.appendChild(script);\n",
" }\n",
"\n",
" /**\n",
" * Handle when an output is cleared or removed\n",
" */\n",
" function handleClearOutput(event, handle) {\n",
" var cell = handle.cell;\n",
"\n",
" var id = cell.output_area._bokeh_element_id;\n",
" var server_id = cell.output_area._bokeh_server_id;\n",
" // Clean up Bokeh references\n",
" if (id !== undefined) {\n",
" Bokeh.index[id].model.document.clear();\n",
" delete Bokeh.index[id];\n",
" }\n",
"\n",
" if (server_id !== undefined) {\n",
" // Clean up Bokeh references\n",
" var cmd = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n",
" cell.notebook.kernel.execute(cmd, {\n",
" iopub: {\n",
" output: function(msg) {\n",
" var element_id = msg.content.text.trim();\n",
" Bokeh.index[element_id].model.document.clear();\n",
" delete Bokeh.index[element_id];\n",
" }\n",
" }\n",
" });\n",
" // Destroy server and session\n",
" var cmd = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n",
" cell.notebook.kernel.execute(cmd);\n",
" }\n",
" }\n",
"\n",
" /**\n",
" * Handle when a new output is added\n",
" */\n",
" function handleAddOutput(event, handle) {\n",
" var output_area = handle.output_area;\n",
" var output = handle.output;\n",
"\n",
" // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n",
" if ((output.output_type != \"display_data\") || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n",
" return\n",
" }\n",
"\n",
" var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n",
"\n",
" if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n",
" toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n",
" // store reference to embed id on output_area\n",
" output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n",
" }\n",
" if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n",
" var bk_div = document.createElement(\"div\");\n",
" bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n",
" var script_attrs = bk_div.children[0].attributes;\n",
" for (var i = 0; i < script_attrs.length; i++) {\n",
" toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n",
" }\n",
" // store reference to server id on output_area\n",
" output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n",
" }\n",
" }\n",
"\n",
" function register_renderer(events, OutputArea) {\n",
"\n",
" function append_mime(data, metadata, element) {\n",
" // create a DOM node to render to\n",
" var toinsert = this.create_output_subarea(\n",
" metadata,\n",
" CLASS_NAME,\n",
" EXEC_MIME_TYPE\n",
" );\n",
" this.keyboard_manager.register_events(toinsert);\n",
" // Render to node\n",
" var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n",
" render(props, toinsert[toinsert.length - 1]);\n",
" element.append(toinsert);\n",
" return toinsert\n",
" }\n",
"\n",
" /* Handle when an output is cleared or removed */\n",
" events.on('clear_output.CodeCell', handleClearOutput);\n",
" events.on('delete.Cell', handleClearOutput);\n",
"\n",
" /* Handle when a new output is added */\n",
" events.on('output_added.OutputArea', handleAddOutput);\n",
"\n",
" /**\n",
" * Register the mime type and append_mime function with output_area\n",
" */\n",
" OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n",
" /* Is output safe? */\n",
" safe: true,\n",
" /* Index of renderer in `output_area.display_order` */\n",
" index: 0\n",
" });\n",
" }\n",
"\n",
" // register the mime type if in Jupyter Notebook environment and previously unregistered\n",
" if (root.Jupyter !== undefined) {\n",
" var events = require('base/js/events');\n",
" var OutputArea = require('notebook/js/outputarea').OutputArea;\n",
"\n",
" if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n",
" register_renderer(events, OutputArea);\n",
" }\n",
" }\n",
"\n",
" \n",
" if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n",
" root._bokeh_timeout = Date.now() + 5000;\n",
" root._bokeh_failed_load = false;\n",
" }\n",
"\n",
" var NB_LOAD_WARNING = {'data': {'text/html':\n",
" \"\\n\"+\n",
" \"
\\n\"+\n",
" \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n",
" \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n",
" \"
\\n\"+\n",
" \"
\\n\"+\n",
" \"- re-rerun `output_notebook()` to attempt to load from CDN again, or
\\n\"+\n",
" \"- use INLINE resources instead, as so:
\\n\"+\n",
" \"
\\n\"+\n",
" \"
\\n\"+\n",
" \"from bokeh.resources import INLINE\\n\"+\n",
" \"output_notebook(resources=INLINE)\\n\"+\n",
" \"
\\n\"+\n",
" \"
\"}};\n",
"\n",
" function display_loaded() {\n",
" var el = document.getElementById(\"c5208578-78c8-4c2a-aa52-f07b8d26c4e6\");\n",
" if (el != null) {\n",
" el.textContent = \"BokehJS is loading...\";\n",
" }\n",
" if (root.Bokeh !== undefined) {\n",
" if (el != null) {\n",
" el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n",
" }\n",
" } else if (Date.now() < root._bokeh_timeout) {\n",
" setTimeout(display_loaded, 100)\n",
" }\n",
" }\n",
"\n",
"\n",
" function run_callbacks() {\n",
" try {\n",
" root._bokeh_onload_callbacks.forEach(function(callback) { callback() });\n",
" }\n",
" finally {\n",
" delete root._bokeh_onload_callbacks\n",
" }\n",
" console.info(\"Bokeh: all callbacks have finished\");\n",
" }\n",
"\n",
" function load_libs(js_urls, callback) {\n",
" root._bokeh_onload_callbacks.push(callback);\n",
" if (root._bokeh_is_loading > 0) {\n",
" console.log(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n",
" return null;\n",
" }\n",
" if (js_urls == null || js_urls.length === 0) {\n",
" run_callbacks();\n",
" return null;\n",
" }\n",
" console.log(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n",
" root._bokeh_is_loading = js_urls.length;\n",
" for (var i = 0; i < js_urls.length; i++) {\n",
" var url = js_urls[i];\n",
" var s = document.createElement('script');\n",
" s.src = url;\n",
" s.async = false;\n",
" s.onreadystatechange = s.onload = function() {\n",
" root._bokeh_is_loading--;\n",
" if (root._bokeh_is_loading === 0) {\n",
" console.log(\"Bokeh: all BokehJS libraries loaded\");\n",
" run_callbacks()\n",
" }\n",
" };\n",
" s.onerror = function() {\n",
" console.warn(\"failed to load library \" + url);\n",
" };\n",
" console.log(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
" document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
" }\n",
" };var element = document.getElementById(\"c5208578-78c8-4c2a-aa52-f07b8d26c4e6\");\n",
" if (element == null) {\n",
" console.log(\"Bokeh: ERROR: autoload.js configured with elementid 'c5208578-78c8-4c2a-aa52-f07b8d26c4e6' but no matching script tag was found. \")\n",
" return false;\n",
" }\n",
"\n",
" var js_urls = [\"https://cdn.pydata.org/bokeh/release/bokeh-0.12.15.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.15.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-tables-0.12.15.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-gl-0.12.15.min.js\"];\n",
"\n",
" var inline_js = [\n",
" function(Bokeh) {\n",
" Bokeh.set_log_level(\"info\");\n",
" },\n",
" \n",
" function(Bokeh) {\n",
" \n",
" },\n",
" function(Bokeh) {\n",
" console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-0.12.15.min.css\");\n",
" Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-0.12.15.min.css\");\n",
" console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.15.min.css\");\n",
" Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.15.min.css\");\n",
" console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-tables-0.12.15.min.css\");\n",
" Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-tables-0.12.15.min.css\");\n",
" }\n",
" ];\n",
"\n",
" function run_inline_js() {\n",
" \n",
" if ((root.Bokeh !== undefined) || (force === true)) {\n",
" for (var i = 0; i < inline_js.length; i++) {\n",
" inline_js[i].call(root, root.Bokeh);\n",
" }if (force === true) {\n",
" display_loaded();\n",
" }} else if (Date.now() < root._bokeh_timeout) {\n",
" setTimeout(run_inline_js, 100);\n",
" } else if (!root._bokeh_failed_load) {\n",
" console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n",
" root._bokeh_failed_load = true;\n",
" } else if (force !== true) {\n",
" var cell = $(document.getElementById(\"c5208578-78c8-4c2a-aa52-f07b8d26c4e6\")).parents('.cell').data().cell;\n",
" cell.output_area.append_execute_result(NB_LOAD_WARNING)\n",
" }\n",
"\n",
" }\n",
"\n",
" if (root._bokeh_is_loading === 0) {\n",
" console.log(\"Bokeh: BokehJS loaded, going straight to plotting\");\n",
" run_inline_js();\n",
" } else {\n",
" load_libs(js_urls, function() {\n",
" console.log(\"Bokeh: BokehJS plotting callback run at\", now());\n",
" run_inline_js();\n",
" });\n",
" }\n",
"}(window));"
],
"application/vnd.bokehjs_load.v0+json": "\n(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n\n if (typeof (root._bokeh_onload_callbacks) === \"undefined\" || force === true) {\n root._bokeh_onload_callbacks = [];\n root._bokeh_is_loading = undefined;\n }\n\n \n\n \n if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n var NB_LOAD_WARNING = {'data': {'text/html':\n \"\\n\"+\n \"
\\n\"+\n \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n \"
\\n\"+\n \"
\\n\"+\n \"- re-rerun `output_notebook()` to attempt to load from CDN again, or
\\n\"+\n \"- use INLINE resources instead, as so:
\\n\"+\n \"
\\n\"+\n \"
\\n\"+\n \"from bokeh.resources import INLINE\\n\"+\n \"output_notebook(resources=INLINE)\\n\"+\n \"
\\n\"+\n \"
\"}};\n\n function display_loaded() {\n var el = document.getElementById(\"c5208578-78c8-4c2a-aa52-f07b8d26c4e6\");\n if (el != null) {\n el.textContent = \"BokehJS is loading...\";\n }\n if (root.Bokeh !== undefined) {\n if (el != null) {\n el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(display_loaded, 100)\n }\n }\n\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) { callback() });\n }\n finally {\n delete root._bokeh_onload_callbacks\n }\n console.info(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(js_urls, callback) {\n root._bokeh_onload_callbacks.push(callback);\n if (root._bokeh_is_loading > 0) {\n console.log(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls == null || js_urls.length === 0) {\n run_callbacks();\n return null;\n }\n console.log(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n root._bokeh_is_loading = js_urls.length;\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n var s = document.createElement('script');\n s.src = url;\n s.async = false;\n s.onreadystatechange = s.onload = function() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.log(\"Bokeh: all BokehJS libraries loaded\");\n run_callbacks()\n }\n };\n s.onerror = function() {\n console.warn(\"failed to load library \" + url);\n };\n console.log(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.getElementsByTagName(\"head\")[0].appendChild(s);\n }\n };var element = document.getElementById(\"c5208578-78c8-4c2a-aa52-f07b8d26c4e6\");\n if (element == null) {\n console.log(\"Bokeh: ERROR: autoload.js configured with elementid 'c5208578-78c8-4c2a-aa52-f07b8d26c4e6' but no matching script tag was found. \")\n return false;\n }\n\n var js_urls = [\"https://cdn.pydata.org/bokeh/release/bokeh-0.12.15.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.15.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-tables-0.12.15.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-gl-0.12.15.min.js\"];\n\n var inline_js = [\n function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\n \n function(Bokeh) {\n \n },\n function(Bokeh) {\n console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-0.12.15.min.css\");\n Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-0.12.15.min.css\");\n console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.15.min.css\");\n Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.15.min.css\");\n console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-tables-0.12.15.min.css\");\n Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-tables-0.12.15.min.css\");\n }\n ];\n\n function run_inline_js() {\n \n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }if (force === true) {\n display_loaded();\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n } else if (force !== true) {\n var cell = $(document.getElementById(\"c5208578-78c8-4c2a-aa52-f07b8d26c4e6\")).parents('.cell').data().cell;\n cell.output_area.append_execute_result(NB_LOAD_WARNING)\n }\n\n }\n\n if (root._bokeh_is_loading === 0) {\n console.log(\"Bokeh: BokehJS loaded, going straight to plotting\");\n run_inline_js();\n } else {\n load_libs(js_urls, function() {\n console.log(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n}(window));"
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"output_notebook()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"# Create the initial dataframe\n",
"index = ['sales','returns','credit fees','rebates','late charges','shipping']\n",
"data = {'amount': [350000,-30000,-7500,-25000,95000,-7000]}\n",
"df = pd.DataFrame(data=data,index=index)\n",
"\n",
"# Determine the total net value by adding the start and all additional transactions\n",
"net = df['amount'].sum()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" amount | \n",
"
\n",
" \n",
" \n",
" \n",
" sales | \n",
" 350000 | \n",
"
\n",
" \n",
" returns | \n",
" -30000 | \n",
"
\n",
" \n",
" credit fees | \n",
" -7500 | \n",
"
\n",
" \n",
" rebates | \n",
" -25000 | \n",
"
\n",
" \n",
" late charges | \n",
" 95000 | \n",
"
\n",
" \n",
" shipping | \n",
" -7000 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" amount\n",
"sales 350000\n",
"returns -30000\n",
"credit fees -7500\n",
"rebates -25000\n",
"late charges 95000\n",
"shipping -7000"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"# Create additional columns that we will use to build the waterfall\n",
"df['running_total'] = df['amount'].cumsum()\n",
"df['y_start'] = df['running_total'] - df['amount']\n",
"\n",
"# Where do we want to place the label\n",
"df['label_pos'] = df['running_total']"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" amount | \n",
" running_total | \n",
" y_start | \n",
" label_pos | \n",
"
\n",
" \n",
" \n",
" \n",
" sales | \n",
" 350000 | \n",
" 350000 | \n",
" 0 | \n",
" 350000 | \n",
"
\n",
" \n",
" returns | \n",
" -30000 | \n",
" 320000 | \n",
" 350000 | \n",
" 320000 | \n",
"
\n",
" \n",
" credit fees | \n",
" -7500 | \n",
" 312500 | \n",
" 320000 | \n",
" 312500 | \n",
"
\n",
" \n",
" rebates | \n",
" -25000 | \n",
" 287500 | \n",
" 312500 | \n",
" 287500 | \n",
"
\n",
" \n",
" late charges | \n",
" 95000 | \n",
" 382500 | \n",
" 287500 | \n",
" 382500 | \n",
"
\n",
" \n",
" shipping | \n",
" -7000 | \n",
" 375500 | \n",
" 382500 | \n",
" 375500 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" amount running_total y_start label_pos\n",
"sales 350000 350000 0 350000\n",
"returns -30000 320000 350000 320000\n",
"credit fees -7500 312500 320000 312500\n",
"rebates -25000 287500 312500 287500\n",
"late charges 95000 382500 287500 382500\n",
"shipping -7000 375500 382500 375500"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# We need to have a net column at the end with the totals and a full bar\n",
"df_net = pd.DataFrame.from_records([(net, net, 0, net)], \n",
" columns=['amount', 'running_total', 'y_start', 'label_pos'],\n",
" index=[\"net\"])\n",
"df = df.append(df_net)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" amount | \n",
" running_total | \n",
" y_start | \n",
" label_pos | \n",
"
\n",
" \n",
" \n",
" \n",
" sales | \n",
" 350000 | \n",
" 350000 | \n",
" 0 | \n",
" 350000 | \n",
"
\n",
" \n",
" returns | \n",
" -30000 | \n",
" 320000 | \n",
" 350000 | \n",
" 320000 | \n",
"
\n",
" \n",
" credit fees | \n",
" -7500 | \n",
" 312500 | \n",
" 320000 | \n",
" 312500 | \n",
"
\n",
" \n",
" rebates | \n",
" -25000 | \n",
" 287500 | \n",
" 312500 | \n",
" 287500 | \n",
"
\n",
" \n",
" late charges | \n",
" 95000 | \n",
" 382500 | \n",
" 287500 | \n",
" 382500 | \n",
"
\n",
" \n",
" shipping | \n",
" -7000 | \n",
" 375500 | \n",
" 382500 | \n",
" 375500 | \n",
"
\n",
" \n",
" net | \n",
" 375500 | \n",
" 375500 | \n",
" 0 | \n",
" 375500 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" amount running_total y_start label_pos\n",
"sales 350000 350000 0 350000\n",
"returns -30000 320000 350000 320000\n",
"credit fees -7500 312500 320000 312500\n",
"rebates -25000 287500 312500 287500\n",
"late charges 95000 382500 287500 382500\n",
"shipping -7000 375500 382500 375500\n",
"net 375500 375500 0 375500"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"# We want to color the positive values gray and the negative red\n",
"df['color'] = 'grey'\n",
"df.loc[df.amount < 0, 'color'] = 'red'\n",
"\n",
"# The 10000 factor is used to make the text positioned correctly.\n",
"# You will need to modify if the values are significantly different\n",
"df.loc[df.amount < 0, 'label_pos'] = df.label_pos - 10000\n",
"df[\"bar_label\"] = df[\"amount\"].map('{:,.0f}'.format)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" amount | \n",
" running_total | \n",
" y_start | \n",
" label_pos | \n",
" color | \n",
" bar_label | \n",
"
\n",
" \n",
" \n",
" \n",
" sales | \n",
" 350000 | \n",
" 350000 | \n",
" 0 | \n",
" 350000 | \n",
" grey | \n",
" 350,000 | \n",
"
\n",
" \n",
" returns | \n",
" -30000 | \n",
" 320000 | \n",
" 350000 | \n",
" 310000 | \n",
" red | \n",
" -30,000 | \n",
"
\n",
" \n",
" credit fees | \n",
" -7500 | \n",
" 312500 | \n",
" 320000 | \n",
" 302500 | \n",
" red | \n",
" -7,500 | \n",
"
\n",
" \n",
" rebates | \n",
" -25000 | \n",
" 287500 | \n",
" 312500 | \n",
" 277500 | \n",
" red | \n",
" -25,000 | \n",
"
\n",
" \n",
" late charges | \n",
" 95000 | \n",
" 382500 | \n",
" 287500 | \n",
" 382500 | \n",
" grey | \n",
" 95,000 | \n",
"
\n",
" \n",
" shipping | \n",
" -7000 | \n",
" 375500 | \n",
" 382500 | \n",
" 365500 | \n",
" red | \n",
" -7,000 | \n",
"
\n",
" \n",
" net | \n",
" 375500 | \n",
" 375500 | \n",
" 0 | \n",
" 375500 | \n",
" grey | \n",
" 375,500 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" amount running_total y_start label_pos color bar_label\n",
"sales 350000 350000 0 350000 grey 350,000\n",
"returns -30000 320000 350000 310000 red -30,000\n",
"credit fees -7500 312500 320000 302500 red -7,500\n",
"rebates -25000 287500 312500 277500 red -25,000\n",
"late charges 95000 382500 287500 382500 grey 95,000\n",
"shipping -7000 375500 382500 365500 red -7,000\n",
"net 375500 375500 0 375500 grey 375,500"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"# Build the Bokeh figure\n",
"\n",
"# Limit the tools to only these three\n",
"TOOLS = \"box_zoom,reset,save\"\n",
"\n",
"# Build the source data off the df dataframe\n",
"source = ColumnDataSource(df)\n",
"\n",
"# Create the figure and assign range values that look good for the data set\n",
"p = figure(tools=TOOLS, x_range=list(df.index), y_range=(0, net+40000), plot_width=800, title = \"Sales Waterfall\")\n",
"p.grid.grid_line_alpha=0.3\n",
"\n",
"# Add the segments\n",
"p.segment(x0='index', y0='y_start', x1=\"index\", y1='running_total', source=source, color=\"color\", line_width=55)\n",
"\n",
"# Format the y-axis as dollars\n",
"p.yaxis[0].formatter = NumeralTickFormatter(format=\"($ 0 a)\")\n",
"p.xaxis.axis_label = \"Transactions\"\n",
"\n",
"# Add the labels\n",
"labels = LabelSet(x='index', y='label_pos', text='bar_label', text_font_size=\"8pt\", level='glyph',\n",
" x_offset=-20, y_offset=0, source=source)\n",
"p.add_layout(labels)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/javascript": [
"(function(root) {\n",
" function embed_document(root) {\n",
" \n",
" var docs_json = {\"0af0f5de-039b-4fad-afdb-3c10bea62d85\":{\"roots\":{\"references\":[{\"attributes\":{},\"id\":\"6bd81e8e-0de2-4ae3-8c30-20f421b2dd30\",\"type\":\"SaveTool\"},{\"attributes\":{\"line_alpha\":{\"value\":0.1},\"line_color\":{\"value\":\"#1f77b4\"},\"line_width\":{\"value\":55},\"x0\":{\"field\":\"index\"},\"x1\":{\"field\":\"index\"},\"y0\":{\"field\":\"y_start\"},\"y1\":{\"field\":\"running_total\"}},\"id\":\"71448bd6-645e-4a33-9756-31c7f64846b6\",\"type\":\"Segment\"},{\"attributes\":{\"source\":{\"id\":\"d8db882f-1b2d-492e-bc29-4e20a3a60f93\",\"type\":\"ColumnDataSource\"}},\"id\":\"c451dabb-9340-4897-831d-04e44366eac9\",\"type\":\"CDSView\"},{\"attributes\":{\"callback\":null,\"factors\":[\"sales\",\"returns\",\"credit fees\",\"rebates\",\"late charges\",\"shipping\",\"net\"]},\"id\":\"c9292009-57d5-4f1d-9f51-d2e707ddf522\",\"type\":\"FactorRange\"},{\"attributes\":{\"data_source\":{\"id\":\"d8db882f-1b2d-492e-bc29-4e20a3a60f93\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"165fc71f-2562-47a7-b881-4256b861111c\",\"type\":\"Segment\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"71448bd6-645e-4a33-9756-31c7f64846b6\",\"type\":\"Segment\"},\"selection_glyph\":null,\"view\":{\"id\":\"c451dabb-9340-4897-831d-04e44366eac9\",\"type\":\"CDSView\"}},\"id\":\"24d78589-d18e-4316-b175-c84760924068\",\"type\":\"GlyphRenderer\"},{\"attributes\":{},\"id\":\"317f4173-5b9d-4935-9bd6-bd9e81bdac34\",\"type\":\"CategoricalTickFormatter\"},{\"attributes\":{\"plot\":null,\"text\":\"Sales Waterfall\"},\"id\":\"7730e0c8-a85f-4390-8fe7-6bc149ec0894\",\"type\":\"Title\"},{\"attributes\":{\"level\":\"glyph\",\"plot\":{\"id\":\"eec691b0-6b6a-45ff-8365-f8d008272cdf\",\"subtype\":\"Figure\",\"type\":\"Plot\"},\"source\":{\"id\":\"d8db882f-1b2d-492e-bc29-4e20a3a60f93\",\"type\":\"ColumnDataSource\"},\"text\":{\"field\":\"bar_label\"},\"text_font_size\":{\"value\":\"8pt\"},\"x\":{\"field\":\"index\"},\"x_offset\":{\"value\":-20},\"y\":{\"field\":\"label_pos\"}},\"id\":\"59e500d7-e38f-42b5-bd3d-9abc29465ec3\",\"type\":\"LabelSet\"},{\"attributes\":{\"below\":[{\"id\":\"708501a1-bcd6-4c2a-b479-0acd33b639ab\",\"type\":\"CategoricalAxis\"}],\"left\":[{\"id\":\"fba3f50b-531d-4a62-9566-8269643227b5\",\"type\":\"LinearAxis\"}],\"plot_width\":800,\"renderers\":[{\"id\":\"708501a1-bcd6-4c2a-b479-0acd33b639ab\",\"type\":\"CategoricalAxis\"},{\"id\":\"95b08957-1353-4515-bdd8-8111566ac731\",\"type\":\"Grid\"},{\"id\":\"fba3f50b-531d-4a62-9566-8269643227b5\",\"type\":\"LinearAxis\"},{\"id\":\"67fade77-8715-4d39-b450-1bc39ec02a26\",\"type\":\"Grid\"},{\"id\":\"c065765e-b568-4156-b067-d6a74ede3676\",\"type\":\"BoxAnnotation\"},{\"id\":\"24d78589-d18e-4316-b175-c84760924068\",\"type\":\"GlyphRenderer\"},{\"id\":\"59e500d7-e38f-42b5-bd3d-9abc29465ec3\",\"type\":\"LabelSet\"}],\"title\":{\"id\":\"7730e0c8-a85f-4390-8fe7-6bc149ec0894\",\"type\":\"Title\"},\"toolbar\":{\"id\":\"1f9674b7-21bf-49d9-b47b-0351b9390ba2\",\"type\":\"Toolbar\"},\"x_range\":{\"id\":\"c9292009-57d5-4f1d-9f51-d2e707ddf522\",\"type\":\"FactorRange\"},\"x_scale\":{\"id\":\"ce6a6ec1-a5e4-402d-b865-85a01aac5449\",\"type\":\"CategoricalScale\"},\"y_range\":{\"id\":\"119ab089-ffdb-4d09-b1bf-f1096b53d80c\",\"type\":\"Range1d\"},\"y_scale\":{\"id\":\"7a47e255-0a18-4971-8d0d-aae1c13b618e\",\"type\":\"LinearScale\"}},\"id\":\"eec691b0-6b6a-45ff-8365-f8d008272cdf\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"attributes\":{},\"id\":\"a0a94fad-4cc8-460b-8c96-cedda411804d\",\"type\":\"ResetTool\"},{\"attributes\":{},\"id\":\"182bca91-c6e9-40e9-8b8a-9817ee08b5c6\",\"type\":\"BasicTicker\"},{\"attributes\":{\"callback\":null,\"end\":415500},\"id\":\"119ab089-ffdb-4d09-b1bf-f1096b53d80c\",\"type\":\"Range1d\"},{\"attributes\":{\"axis_label\":\"Transactions\",\"formatter\":{\"id\":\"317f4173-5b9d-4935-9bd6-bd9e81bdac34\",\"type\":\"CategoricalTickFormatter\"},\"plot\":{\"id\":\"eec691b0-6b6a-45ff-8365-f8d008272cdf\",\"subtype\":\"Figure\",\"type\":\"Plot\"},\"ticker\":{\"id\":\"4b79d5b6-2574-4383-8636-0ec22fba944f\",\"type\":\"CategoricalTicker\"}},\"id\":\"708501a1-bcd6-4c2a-b479-0acd33b639ab\",\"type\":\"CategoricalAxis\"},{\"attributes\":{},\"id\":\"ce6a6ec1-a5e4-402d-b865-85a01aac5449\",\"type\":\"CategoricalScale\"},{\"attributes\":{},\"id\":\"4b79d5b6-2574-4383-8636-0ec22fba944f\",\"type\":\"CategoricalTicker\"},{\"attributes\":{\"format\":\"($ 0 a)\"},\"id\":\"3c75fd4f-26c3-42c8-b9a9-515fea1ff86a\",\"type\":\"NumeralTickFormatter\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"amount\",\"running_total\",\"y_start\",\"label_pos\",\"color\",\"bar_label\",\"index\"],\"data\":{\"amount\":[350000,-30000,-7500,-25000,95000,-7000,375500],\"bar_label\":[\"350,000\",\"-30,000\",\"-7,500\",\"-25,000\",\"95,000\",\"-7,000\",\"375,500\"],\"color\":[\"grey\",\"red\",\"red\",\"red\",\"grey\",\"red\",\"grey\"],\"index\":[\"sales\",\"returns\",\"credit fees\",\"rebates\",\"late charges\",\"shipping\",\"net\"],\"label_pos\":[350000,310000,302500,277500,382500,365500,375500],\"running_total\":[350000,320000,312500,287500,382500,375500,375500],\"y_start\":[0,350000,320000,312500,287500,382500,0]},\"selected\":null,\"selection_policy\":null},\"id\":\"d8db882f-1b2d-492e-bc29-4e20a3a60f93\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"grid_line_alpha\":{\"value\":0.3},\"plot\":{\"id\":\"eec691b0-6b6a-45ff-8365-f8d008272cdf\",\"subtype\":\"Figure\",\"type\":\"Plot\"},\"ticker\":{\"id\":\"4b79d5b6-2574-4383-8636-0ec22fba944f\",\"type\":\"CategoricalTicker\"}},\"id\":\"95b08957-1353-4515-bdd8-8111566ac731\",\"type\":\"Grid\"},{\"attributes\":{\"line_color\":{\"field\":\"color\"},\"line_width\":{\"value\":55},\"x0\":{\"field\":\"index\"},\"x1\":{\"field\":\"index\"},\"y0\":{\"field\":\"y_start\"},\"y1\":{\"field\":\"running_total\"}},\"id\":\"165fc71f-2562-47a7-b881-4256b861111c\",\"type\":\"Segment\"},{\"attributes\":{\"dimension\":1,\"grid_line_alpha\":{\"value\":0.3},\"plot\":{\"id\":\"eec691b0-6b6a-45ff-8365-f8d008272cdf\",\"subtype\":\"Figure\",\"type\":\"Plot\"},\"ticker\":{\"id\":\"182bca91-c6e9-40e9-8b8a-9817ee08b5c6\",\"type\":\"BasicTicker\"}},\"id\":\"67fade77-8715-4d39-b450-1bc39ec02a26\",\"type\":\"Grid\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":{\"value\":0.5},\"fill_color\":{\"value\":\"lightgrey\"},\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":{\"value\":1.0},\"line_color\":{\"value\":\"black\"},\"line_dash\":[4,4],\"line_width\":{\"value\":2},\"plot\":null,\"render_mode\":\"css\",\"right_units\":\"screen\",\"top_units\":\"screen\"},\"id\":\"c065765e-b568-4156-b067-d6a74ede3676\",\"type\":\"BoxAnnotation\"},{\"attributes\":{\"active_drag\":\"auto\",\"active_inspect\":\"auto\",\"active_scroll\":\"auto\",\"active_tap\":\"auto\",\"tools\":[{\"id\":\"7b11336c-e6e0-4c1f-b1be-9868849934f0\",\"type\":\"BoxZoomTool\"},{\"id\":\"a0a94fad-4cc8-460b-8c96-cedda411804d\",\"type\":\"ResetTool\"},{\"id\":\"6bd81e8e-0de2-4ae3-8c30-20f421b2dd30\",\"type\":\"SaveTool\"}]},\"id\":\"1f9674b7-21bf-49d9-b47b-0351b9390ba2\",\"type\":\"Toolbar\"},{\"attributes\":{},\"id\":\"7a47e255-0a18-4971-8d0d-aae1c13b618e\",\"type\":\"LinearScale\"},{\"attributes\":{\"overlay\":{\"id\":\"c065765e-b568-4156-b067-d6a74ede3676\",\"type\":\"BoxAnnotation\"}},\"id\":\"7b11336c-e6e0-4c1f-b1be-9868849934f0\",\"type\":\"BoxZoomTool\"},{\"attributes\":{\"formatter\":{\"id\":\"3c75fd4f-26c3-42c8-b9a9-515fea1ff86a\",\"type\":\"NumeralTickFormatter\"},\"plot\":{\"id\":\"eec691b0-6b6a-45ff-8365-f8d008272cdf\",\"subtype\":\"Figure\",\"type\":\"Plot\"},\"ticker\":{\"id\":\"182bca91-c6e9-40e9-8b8a-9817ee08b5c6\",\"type\":\"BasicTicker\"}},\"id\":\"fba3f50b-531d-4a62-9566-8269643227b5\",\"type\":\"LinearAxis\"}],\"root_ids\":[\"eec691b0-6b6a-45ff-8365-f8d008272cdf\"]},\"title\":\"Bokeh Application\",\"version\":\"0.12.15\"}};\n",
" var render_items = [{\"docid\":\"0af0f5de-039b-4fad-afdb-3c10bea62d85\",\"elementid\":\"5f102e27-9bf3-43e2-9297-f719355d68b2\",\"modelid\":\"eec691b0-6b6a-45ff-8365-f8d008272cdf\"}];\n",
" root.Bokeh.embed.embed_items_notebook(docs_json, render_items);\n",
"\n",
" }\n",
" if (root.Bokeh !== undefined) {\n",
" embed_document(root);\n",
" } else {\n",
" var attempts = 0;\n",
" var timer = setInterval(function(root) {\n",
" if (root.Bokeh !== undefined) {\n",
" embed_document(root);\n",
" clearInterval(timer);\n",
" }\n",
" attempts++;\n",
" if (attempts > 100) {\n",
" console.log(\"Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing\")\n",
" clearInterval(timer);\n",
" }\n",
" }, 10, root)\n",
" }\n",
"})(window);"
],
"application/vnd.bokehjs_exec.v0+json": ""
},
"metadata": {
"application/vnd.bokehjs_exec.v0+json": {
"id": "eec691b0-6b6a-45ff-8365-f8d008272cdf"
}
},
"output_type": "display_data"
}
],
"source": [
"show(p)"
]
}
],
"metadata": {
"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.6.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}