{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Based on:\n", "- https://github.com/bloomberg/bqplot/blob/master/examples/Applications/Wealth%20of%20Nations.ipynb\n", "\n", "This is basically the same notebook execpt the last couple of cells that use the Jupyter-flex layout controls" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "parameters" ] }, "outputs": [], "source": [ "flex_title = \"Wealth of Nations\"\n", "flex_author = \"built using jupyter-flex\"\n", "flex_source_link = \"https://github.com/danielfrg/jupyter-flex/blob/master/examples/wealth-of-nations.ipynb\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "import numpy as np\n", "import pandas as pd\n", "\n", "from bqplot import (\n", " LogScale, LinearScale, OrdinalColorScale, ColorAxis,\n", " Axis, Scatter, Lines, CATEGORY10, Label, Figure, Tooltip\n", ")\n", "\n", "import ipywidgets as widgets\n", "from ipywidgets import HBox, VBox, IntSlider, Play, jslink" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "initial_year = 1800" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Cleaning and Formatting JSON Data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data = pd.read_json(\"https://raw.githubusercontent.com/bloomberg/bqplot/master/examples/data_files/nations.json\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def clean_data(data):\n", " for column in ['income', 'lifeExpectancy', 'population']:\n", " data = data.drop(data[data[column].apply(len) <= 4].index)\n", " return data\n", "\n", "def extrap_interp(data):\n", " data = np.array(data)\n", " x_range = np.arange(1800, 2009, 1.)\n", " y_range = np.interp(x_range, data[:, 0], data[:, 1])\n", " return y_range\n", "\n", "def extrap_data(data):\n", " for column in ['income', 'lifeExpectancy', 'population']:\n", " data[column] = data[column].apply(extrap_interp)\n", " return data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data = clean_data(data)\n", "data = extrap_data(data)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "income_min, income_max = np.min(data['income'].apply(np.min)), np.max(data['income'].apply(np.max))\n", "life_exp_min, life_exp_max = np.min(data['lifeExpectancy'].apply(np.min)), np.max(data['lifeExpectancy'].apply(np.max))\n", "pop_min, pop_max = np.min(data['population'].apply(np.min)), np.max(data['population'].apply(np.max))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_data(year):\n", " year_index = year - 1800\n", " income = data['income'].apply(lambda x: x[year_index])\n", " life_exp = data['lifeExpectancy'].apply(lambda x: x[year_index])\n", " pop = data['population'].apply(lambda x: x[year_index])\n", " return income, life_exp, pop" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Creating the Tooltip to display the required fields\n", "\n", "bqplot's native Tooltip allows us to simply display the data fields we require on a mouse-interaction." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tt = Tooltip(fields=['name', 'x', 'y'], labels=['Country Name', 'Income per Capita', 'Life Expectancy'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Creating the Label to display the year\n", "\n", "Staying true to the d3 recreation of the talk, we place a Label widget in the bottom-right of the Figure (it inherits the Figure co-ordinates when no scale is passed to it). With enable_move set to True, the Label can be dragged around." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "year_label = Label(x=[0.75], y=[0.10], default_size=46, font_weight='bolder', colors=['orange'],\n", " text=[str(initial_year)], enable_move=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Defining Axes and Scales\n", "\n", "The inherent skewness of the income data favors the use of a LogScale. Also, since the color coding by regions does not follow an ordering, we use the OrdinalColorScale." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x_sc = LogScale(min=income_min, max=income_max)\n", "y_sc = LinearScale(min=life_exp_min, max=life_exp_max)\n", "c_sc = OrdinalColorScale(domain=data['region'].unique().tolist(), colors=CATEGORY10[:6])\n", "size_sc = LinearScale(min=pop_min, max=pop_max)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ax_y = Axis(label='Life Expectancy', scale=y_sc, orientation='vertical', side='left', grid_lines='solid')\n", "ax_x = Axis(label='Income per Capita', scale=x_sc, grid_lines='solid')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Creating the Scatter Mark with the appropriate size and color parameters passed\n", "\n", "To generate the appropriate graph, we need to pass the population of the country to the size attribute and its region to the color attribute." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Start with the first year's data\n", "cap_income, life_exp, pop = get_data(initial_year)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wealth_scat = Scatter(x=cap_income, y=life_exp, color=data['region'], size=pop,\n", " names=data['name'], display_names=False,\n", " scales={'x': x_sc, 'y': y_sc, 'color': c_sc, 'size': size_sc},\n", " default_size=4112, tooltip=tt, animate=True, stroke='Black',\n", " unhovered_style={'opacity': 0.5})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "nation_line = Lines(x=data['income'][0], y=data['lifeExpectancy'][0], colors=['Gray'],\n", " scales={'x': x_sc, 'y': y_sc}, visible=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Creating the Figure" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "time_interval = 1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = Figure(marks=[wealth_scat, year_label, nation_line], axes=[ax_x, ax_y],\n", " title='Health and Wealth of Nations', animation_duration=time_interval)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Using a Slider to allow the user to change the year and a button for animation\n", "\n", "Here we see how we can seamlessly integrate bqplot into the jupyter widget infrastructure." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "year_slider = IntSlider(min=1800, max=2008, step=1, value=initial_year)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When the hovered_point of the Scatter plot is changed (i.e. when the user hovers over a different element), the entire path of that country is displayed by making the Lines object visible and setting it's x and y attributes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def hover_changed(change):\n", " if change.new is not None:\n", " nation_line.x = data[data['name'] == wealth_scat.names[change.new]]['income'].values[0]\n", " nation_line.y = data[data['name'] == wealth_scat.names[change.new]]['lifeExpectancy'].values[0]\n", " nation_line.visible = True\n", " else:\n", " nation_line.visible = False\n", " \n", "wealth_scat.observe(hover_changed, 'hovered_point')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On the slider value callback (a function that is triggered everytime the value of the slider is changed) we change the x, y and size co-ordinates of the Scatter. We also update the text of the Label to reflect the current year." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def year_changed(change):\n", " wealth_scat.x, wealth_scat.y, wealth_scat.size = get_data(year_slider.value)\n", " year_label.text = [str(year_slider.value)]\n", "\n", "year_slider.observe(year_changed, 'value')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Add an animation button" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "play_button = Play(min=1800, max=2008, interval=time_interval)\n", "jslink((play_button, 'value'), (year_slider, 'value'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Displaying the GUI" ] }, { "cell_type": "markdown", "metadata": { "tags": [ "sidebar" ] }, "source": [ "# Sidebar" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "lbl1 = widgets.Label(\"Controls\")\n", "lbl2 = widgets.Label(\"Year\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "body" ] }, "outputs": [], "source": [ "VBox([lbl1, play_button, lbl2, year_slider])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# App" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "body" ] }, "outputs": [], "source": [ "fig" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "celltoolbar": "Tags", "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.6" } }, "nbformat": 4, "nbformat_minor": 4 }