Developing Advanced User Interfaces

Using Jupyter Widgets, Pandas Dataframes and Matplotlib to create advanced user interfaces.

While BPTK-Py offers a number of high-level functions to quickly plot equations (such as bptk.plot_scenarios) or create a dashboard (e.g. bptk.dashboard), you may sometimes be in a situation when you want to create more sophisticated plots (e.g. plots with two axes) or a more sophisticated interface dashboard for your simulation.

This is actually quite easy, because BPTK-Py’s high-level functions already utilize some very powerfull open source libraries for data management, plotting and dashboards: Pandas, Matplotlib and Jupyter Widgets.

In order to harness the full power of these libraries, you only need to understand how to make the data generated by BPTK-Py available to them. This How To illustrates this using a neat little simulation of customer acquisition strategies. You don’t need to understand the simulation to follow this document, but if you are interested you can read more about it on our blog.

Advanced Plotting

We’ll start with some advanced plotting of simulation results.

## Load the BPTK Package
from BPTK_Py.bptk import bptk

bptk = bptk()

BPTK-Py’s workhorse for creating plots is the bptk.plot_scenariosfunction. The function generates all the data you would like to plot using the simulation defined by the scenario manager and the settings defined by the scenarios. The data are stored in a Pandas dataframe. When it comes to plotting the results, the framework uses Matplotlib. To illustrate this, we will recreate the plot below directly from the underlying data:

bptk.plot_scenarios(
       scenario_managers=["smCustomerAcquisition"],
       scenarios=["base"],
       equations=['customers'],
       title="Base",
       freq="M",
       x_label="Time",
       y_label="No. of Customers"
       )
../../../_images/output_5_0.png

You can access the data generated by a scenario by saving it into a dataframe. You can do this by adding the return_df flag to bptk.plot_scenario:

df=bptk.plot_scenarios(
       scenario_managers=["smCustomerAcquisition"],
       scenarios=["base"],
       equations=['customers'],
       title="Base",
       freq="M",
       x_label="Time",
       y_label="No. of Customers",
       return_df=True
       )

The dataframe is indexed by time and stores the equations (in SD models) or agent properties (in Agent-based models) in the columns

df[0:10] # just show the first ten items
customers
t
0 0.000000
1 800.000000
2 1599.893333
3 2399.680014
4 3199.360057
5 3998.933476
6 4798.400284
7 5597.760498
8 6397.014130
9 7196.161194

The frameworks bptk.plot_scenarios method first runs the simulation using the setting defined in the scenario and stores the data in a dataframe. It then plots the dataframe using Pandas df.plotmethod.

We can do the same:

subplot=df.plot(None,"customers")
../../../_images/output_11_0.png

The plot above doesn’t look quite as neat as the plots created by bptk.plot_scenarios– this is because the framework applies some styling information. The styling information is stored in BPTK_Py.config, and you can access (and modify) it there.

Now let’s apply the config to df.plot:

import BPTK_Py.config as config

subplot=df.plot(kind=config.configuration["kind"],
                       alpha=config.configuration["alpha"], stacked=config.configuration["stacked"],
                                                          figsize=config.configuration["figsize"],
                                                          title="Base",
                                                          color=config.configuration["colors"],
                                                          lw=config.configuration["linewidth"])
../../../_images/output_13_0.png

Yes! We’ve recreated the plot from the high level btpk.plot_scenarios method using basic plotting functions.

Now let’s do something that currently isn’t possible using the high-level BPTK-Py methods - let’s create a graph that has two axes.

This is useful when you want to show the results of two equations at the same time, but they have different orders of magnitudes. For instance in the plot below, the number of customers is much smaller than the profit made, so the customer graph looks like a straight line. But it would still be intersting to be able to compare the two graphs.

bptk.plot_scenarios(
       scenario_managers=["smCustomerAcquisition"],
       scenarios=["base"],
       equations=['customers','profit'],
       title="Base",
       freq="M",
       x_label="Time",
       y_label="No. of Customers"
       )
../../../_images/output_15_0.png

As before, we collect the data in a dataframe.

df=bptk.plot_scenarios(
       scenario_managers=["smCustomerAcquisition"],
       scenarios=["base"],
       equations=['customers','profit'],
       title="Base",
       freq="M",
       x_label="Time",
       y_label="No. of Customers",
       return_df = True
       )
df[0:10]
customers profit
t
0 0.000000 -1.000000e+06
1 800.000000 -1.010000e+06
2 1599.893333 -1.016000e+06
3 2399.680014 -1.018001e+06
4 3199.360057 -1.016002e+06
5 3998.933476 -1.010005e+06
6 4798.400284 -1.000011e+06
7 5597.760498 -9.860187e+05
8 6397.014130 -9.680299e+05
9 7196.161194 -9.460448e+05

Plotting two axes is easy in Pandas (which itself uses the Matplotlib library):

ax = df.plot(None,'customers', kind=config.configuration["kind"],
                       alpha=config.configuration["alpha"], stacked=config.configuration["stacked"],
                                                          figsize=config.configuration["figsize"],
                                                          title="Profit vs. Customers",
                                                          color=config.configuration["colors"],
                                                          lw=config.configuration["linewidth"])
# ax is a Matplotlib Axes object

ax1 = ax.twinx()

# Matplotlib.axes.Axes.twinx creates a twin y-axis.

plot =df.plot(None,'profit',ax=ax1)
../../../_images/output_20_0.png

Voila! This is actually quite easy one you understand how to access the data (and of course a little knowledge of Pandas and Matplotlib is also useful). If you were writing a document that needed a lot of plots of this kind, you could create your own high-level function to avoide having to copy and paste the code above multiple times.

Advanced Interactive User Interfaces

Now let’s try something a little more challenging: Let’s build a dashboard for our simulation that let’s you manipulate some of the scenrio settings interactively and plots results in tabs.

Note: You need to have widgets enabled in Jupyter for the following to work. Please check the BPTK-Py installation instructions or refer to the Jupyter Widgets documentation

First, we need to understand how to create tabs. For this we need to import the ipywidget Library and we also need to access Matplotlib’s pyplot

%matplotlib inline
import matplotlib.pyplot as plt
from ipywidgets import interact
import ipywidgets as widgets

Then we can create some tabs that display scenario results as follows:

out1 = widgets.Output()
out2 = widgets.Output()

tab = widgets.Tab(children = [out1, out2])
tab.set_title(0, 'Customers')
tab.set_title(1, 'Profit')
display(tab)

with out1:
    # turn of pyplot's interactive mode to ensure the plot is not created directly
    plt.ioff()
    # create the plot, but don't show it yet
    bptk.plot_scenarios(
        scenario_managers=["smCustomerAcquisition"],
        scenarios=["hereWeGo"],
        equations=['customers'],
        title="Here We Go",
        freq="M",
        x_label="Time",
        y_label="No. of Customers"
        )
    # show the plot
    plt.show()
    # turn interactive mode on again
    plt.ion()

with out2:
    plt.ioff()
    bptk.plot_scenarios(
        scenario_managers=["smCustomerAcquisition"],
        scenarios=["hereWeGo"],
        equations=['profit'],
        title="Here We Go",
        freq="M",
        x_label="Time",
        y_label="Euro"
        )
    plt.show()
    plt.ion()
Tab(children=(Output(), Output()), _titles={'0': 'Customers', '1': 'Profit'})

That was easy! The only thing you really need to understand is to turn interactive plotting in pyplot off before creating the tabs and then turn it on again to create the plots. If you forget to do that, the plots appear above the tabs (try it and see!).

In the next step, we need to add some sliders to manipulate the following scenario settings:

  • Referrals

  • Referral Free Months

  • Referral Program Adoption %

  • Advertising Success %

Creating a slider for the referrals is easy using the integer slider from the ipywidgets widget library:

widgets.IntSlider(
    value=7,
    min=0,
    max=15,
    step=1,
    description='Referrals:',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='d'
)
IntSlider(value=7, continuous_update=False, description='Referrals:', max=15)

When manipulating a simulation model, we mostly want to start with a particular scenario and then manipulate some of the scenario settings using interactive widgets. Let’s set up a new scenario for this purpose and call it interactiveScenario:

bptk.register_scenarios(scenario_manager="smCustomerAcquisition", scenarios=
                  {
                      "interactiveScenario":{
                          "constants":{
                             "referrals":0,
                              "advertisingSuccessPct":0.1,
                              "referralFreeMonths":3,
                              "referralProgamAdoptionPct":10
                            }
                      }
                }
)

We can then access the scenario using bptk.scenario_manager_factory.get_scenarios:

scenario = bptk.get_scenario("smCustomerAcquisition","interactiveScenario")
scenario.constants
{'referrals': 0,
 'advertisingSuccessPct': 0.1,
 'referralFreeMonths': 3,
 'referralProgamAdoptionPct': 10}
bptk.plot_scenarios(scenario_managers=["smCustomerAcquisition"],
        scenarios=["interactiveScenario"],
        equations=['profit'],
        title="Interactive Scenario",
        freq="M",
        x_label="Time",
        y_label="Euro"
        )
../../../_images/output_36_0.png

The scenario constants can be accessed in the constants variable:

Now we have all the right pieces, we can put them together using the interact function.

@interact(advertising_success_pct=widgets.FloatSlider(
    value=0.1,
    min=0,
    max=1,
    step=0.01,
    continuous_update=False,
    description='Advertising Success Pct'
))
def dashboard(advertising_success_pct):
    scenario= bptk.get_scenario("smCustomerAcquisition","interactiveScenario")

    scenario.constants["advertisingSuccessPct"]=advertising_success_pct
    bptk.reset_simulation_model(scenario_manager="smCustomerAcquisition",
                                                             scenario="interactiveScenario")
    bptk.plot_scenarios(scenario_managers=["smCustomerAcquisition"],
        scenarios=["interactiveScenario"],
        equations=['profit'],
        title="Interactive Scenario",
        freq="M",
        x_label="Time",
        y_label="Euro"
        )
../../../_images/dashboard.png

Now let’s combine this with the tabs from above.

out1 = widgets.Output()
out2 = widgets.Output()


tab = widgets.Tab(children = [out1, out2])
tab.set_title(0, 'Customers')
tab.set_title(1, 'Profit')
display(tab)

@interact(advertising_success_pct=widgets.FloatSlider(
    value=0.1,
    min=0,
    max=10,
    step=0.01,
    continuous_update=False,
    description='Advertising Success Pct'
))
def dashboardWithTabs(advertising_success_pct):
    scenario= bptk.get_scenario("smCustomerAcquisition","interactiveScenario")

    scenario.constants["advertisingSuccessPct"]=advertising_success_pct
    bptk.reset_simulation_model(scenario_manager="smCustomerAcquisition",
                                                             scenario="interactiveScenario")



    with out1:
        # turn of pyplot's interactive mode to ensure the plot is not created directly
        plt.ioff()
        # clear the widgets output ... otherwise we will end up with a long list of plots, one for each change of settings

        # create the plot, but don't show it yet
        bptk.plot_scenarios(
            scenario_managers=["smCustomerAcquisition"],
            scenarios=["interactiveScenario"],
            equations=['customers'],
            title="Interactive Scenario",
            freq="M",
            x_label="Time",
            y_label="No. of Customers"
            )
        # show the plot
        out1.clear_output()
        plt.show()
        # turn interactive mode on again
        plt.ion()

    with out2:
        plt.ioff()
        out2.clear_output()
        bptk.plot_scenarios(
            scenario_managers=["smCustomerAcquisition"],
            scenarios=["interactiveScenario"],
            equations=['profit'],
            title="Interactive Scenario",
            freq="M",
            x_label="Time",
            y_label="Euro"
            )
        plt.show()
        plt.ion()
../../../_images/dashboard_with_tabs.png