Gapminders#

import param
import numpy as np 
import pandas as pd
import panel as pn

import altair as alt
import plotly.graph_objs as go
import plotly.io as pio
import matplotlib.pyplot as plt

pn.extension('vega', 'plotly', defer_load=True, template='fast')
import hvplot.pandas

Configuration#

Let us start by configuring some high-level variables and configure the template:

XLABEL = 'GDP per capita (2000 dollars)'
YLABEL = 'Life expectancy (years)'
YLIM = (20, 90)
ACCENT = "#00A170"

PERIOD = 1000 # milliseconds

pn.state.template.param.update(
    site_url="https://panel.holoviz.org",
    title="Hans Rosling's Gapminder",
    header_background=ACCENT,
    accent_base_color=ACCENT,
    favicon="static/extensions/panel/images/favicon.ico",
    theme_toggle=False
)
<param.parameterized._ParametersRestorer object at 0x1133022d0>

Extract the dataset#

First, we’ll get the data into a Pandas dataframe. We use the built in cache to speed up the app.

@pn.cache
def get_dataset():
    url = 'https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv'
    return pd.read_csv(url)

dataset = get_dataset()

YEARS = [int(year) for year in dataset.year.unique()]

dataset.sample(10)
country year pop continent lifeExp gdpPercap
776 Italy 1992 56840847.0 Europe 77.440 22013.644860
445 Ecuador 1957 4058385.0 Americas 51.356 3780.546651
1292 Rwanda 1992 7290203.0 Africa 23.599 737.068595
1146 Norway 1982 4114787.0 Europe 75.970 26298.635310
881 Lesotho 1977 1251524.0 Africa 52.208 745.369541
1424 Spain 1992 39549438.0 Europe 77.570 18603.064520
1605 United Kingdom 1997 58808266.0 Europe 77.218 26074.531360
1274 Romania 1962 18680721.0 Europe 66.800 4734.997586
282 Chile 1982 11487112.0 Americas 70.565 5095.665738
560 Gambia 1992 1025384.0 Africa 52.644 665.624413

Set up widgets and description#

Next we will set up a periodic callback to allow cycling through the years, set up the widgets to control the application and write an introduction:

def play():
    if year.value == YEARS[-1]:
        year.value = YEARS[0]
        return

    index = YEARS.index(year.value)
    year.value = YEARS[index+1]    

year = pn.widgets.DiscreteSlider(
    value=YEARS[-1], options=YEARS, name="Year", width=280
)
show_legend = pn.widgets.Checkbox(value=True, name="Show Legend")

periodic_callback = pn.state.add_periodic_callback(play, start=False, period=PERIOD)
player = pn.widgets.Checkbox.from_param(periodic_callback.param.running, name="Autoplay")

widgets = pn.Column(year, player, show_legend, margin=(0,15))

desc = """## 🎓 Info

The [Panel](http://panel.holoviz.org) library from [HoloViz](http://holoviz.org)
lets you make widget-controlled apps and dashboards from a wide variety of 
plotting libraries and data types. Here you can try out four different plotting libraries
controlled by a couple of widgets, for Hans Rosling's 
[gapminder](https://demo.bokeh.org/gapminder) example.

Source: [pyviz-topics - gapminder](https://github.com/pyviz-topics/examples/blob/master/gapminders/gapminders.ipynb)
"""

settings = pn.Column(
    "## ⚙️ Settings", widgets, desc,
    sizing_mode='stretch_width'
).servable(area='sidebar')

settings

Define plotting functions#

Now let’s define helper functions and functions to plot this dataset with Matplotlib, Plotly, Altair, and hvPlot (using HoloViews and Bokeh).

@pn.cache
def get_data(year):
    df = dataset[(dataset.year==year) & (dataset.gdpPercap < 10000)].copy()
    df['size'] = np.sqrt(df['pop']*2.666051223553066e-05)
    df['size_hvplot'] = df['size']*6
    return df

def get_title(library, year):
    return f"{library}: Life expectancy vs. GDP, {year}"

def get_xlim(data):
    return (data['gdpPercap'].min()-100,data['gdpPercap'].max()+1000)

@pn.cache
def mpl_view(year=1952, show_legend=True):
    data = get_data(year)
    title = get_title("Matplotlib", year)
    xlim = get_xlim(data)

    plot = plt.figure(figsize=(10, 6), facecolor=(0, 0, 0, 0))
    ax = plot.add_subplot(111)
    ax.set_xscale("log")
    ax.set_title(title)
    ax.set_xlabel(XLABEL)
    ax.set_ylabel(YLABEL)
    ax.set_ylim(YLIM)
    ax.set_xlim(xlim)

    for continent, df in data.groupby('continent'):
        ax.scatter(df.gdpPercap, y=df.lifeExp, s=df['size']*5,
                   edgecolor='black', label=continent)

    if show_legend:
        ax.legend(loc=4)

    plt.close(plot)
    return plot

pio.templates.default = None

@pn.cache
def plotly_view(year=1952, show_legend=True):
    data = get_data(year)
    title = get_title("Plotly", year)
    xlim = get_xlim(data)

    traces = []
    for continent, df in data.groupby('continent'):
        marker=dict(symbol='circle', sizemode='area', sizeref=0.1, size=df['size'], line=dict(width=2))
        traces.append(go.Scatter(x=df.gdpPercap, y=df.lifeExp, mode='markers', marker=marker, name=continent, text=df.country))

    axis_opts = dict(gridcolor='rgb(255, 255, 255)', zerolinewidth=1, ticklen=5, gridwidth=2)
    layout = go.Layout(
        title=title, showlegend=show_legend,
        xaxis=dict(title=XLABEL, type='log', **axis_opts),
        yaxis=dict(title=YLABEL, **axis_opts),
        autosize=True, paper_bgcolor='rgba(0,0,0,0)',
    )
    
    return go.Figure(data=traces, layout=layout)

@pn.cache
def altair_view(year=1952, show_legend=True, height="container", width="container"):
    data = get_data(year)
    title = get_title("Altair/ Vega", year)
    xlim = get_xlim(data)
    legend= ({} if show_legend else {'legend': None})
    return (
        alt.Chart(data)
            .mark_circle().encode(
                alt.X('gdpPercap:Q', scale=alt.Scale(type='log'), axis=alt.Axis(title=XLABEL)),
                alt.Y('lifeExp:Q', scale=alt.Scale(zero=False, domain=YLIM), axis=alt.Axis(title=YLABEL)),
                size=alt.Size('pop:Q', scale=alt.Scale(type="log"), legend=None),
                color=alt.Color('continent', scale=alt.Scale(scheme="category10"), **legend),
                tooltip=['continent','country'])
            .configure_axis(grid=False)
            .properties(title=title, height=height, width=width, background='rgba(0,0,0,0)') 
            .configure_view(fill="white")
            .interactive()
    )

@pn.cache
def hvplot_view(year=1952, show_legend=True):
    data = get_data(year)
    title = get_title("hvPlot/ Bokeh", year)
    xlim = get_xlim(data)
    return data.hvplot.scatter(
        'gdpPercap', 'lifeExp', by='continent', s='size_hvplot', alpha=0.8,
        logx=True, title=title, responsive=True, legend='bottom_right',
        hover_cols=['country'], ylim=YLIM, xlim=xlim, ylabel=YLABEL, xlabel=XLABEL
    )

Bind the plot functions to the widgets#

mpl_view    = pn.bind(mpl_view,    year=year, show_legend=show_legend)
plotly_view = pn.bind(plotly_view, year=year, show_legend=show_legend)
altair_view = pn.bind(altair_view, year=year, show_legend=show_legend)
hvplot_view = pn.bind(hvplot_view, year=year, show_legend=show_legend)

plots = pn.GridBox(
    pn.pane.HoloViews(hvplot_view, sizing_mode='stretch_both', margin=10),
    pn.pane.Plotly(plotly_view, sizing_mode='stretch_both', margin=10),
    pn.pane.Matplotlib(mpl_view, format='png', sizing_mode='scale_both', tight=True, margin=10),
    pn.pane.Vega(altair_view, sizing_mode='stretch_both', margin=10),
    ncols=2,
    sizing_mode="stretch_both"
).servable()

plots