Creating an Interactive Animation for Parameter Optimisation using Plot.ly







Creating Interactive Animation for Parameter Optimisation using Plot.ly

Introduction

This post demonstrates how to create an interactive and animated plot of results over a changing parameter.

For this, we are replicating the example for showing GDP per capita of countries over time provided in the examples here and shown below (https://plot.ly/python/animations/).

The prerequisits to achive this are Python and Plot.ly (https://plot.ly/python/getting-started/)

This is by no means the cleanest or prettiest method, I have simply forced my data into the example and, aside from changing a few axis labels, made no significant changes to the code from the example. I have created the visual with the idea that parameter optimisation it is not always for publishing but rather the exploration stage of analysis and so must be as quick and as easy as possible to create.

If you would like to use the visual for your report I would suggest editing the formatting slightly but otherwise the creation method can be used similarly.

If you have any questions, please feel free to comment below.

Gapminder Example

We will be re-applying the following technique to create the interactive chart. First, we need to observe the data that is being used. In this example, a scatter plot is created with the following:

  • Life expectancy on the x axis
  • GDP per capita on the y axis
  • Size is relative to population of country
  • Coloured by continent
  • Tracked across years (animation and slider)
Out[12]:
country year pop continent lifeExp gdpPercap
0 Afghanistan 1952 8425333.0 Asia 28.801 779.445314
1 Afghanistan 1957 9240934.0 Asia 30.332 820.853030
2 Afghanistan 1962 10267083.0 Asia 31.997 853.100710
3 Afghanistan 1967 11537966.0 Asia 34.020 836.197138
4 Afghanistan 1972 13079460.0 Asia 36.088 739.981106

Applying our Data

For this to work, I have exported my results into a data table with a specific format. As shown below, there are three columns, Alpha, Epsilon and V. Alpha is the parameter I am changing, Epsilon is the nth time the model was ran and V is my output.

Therefore, my table is structed by the output for each run when decreasing my parameter between 1 and 0. I have also included the raw .csv file to download if you wish to see this is more detail.

VforInteractiveGraph2

In [20]:
from plotly.offline import init_notebook_mode, iplot
from IPython.display import display, HTML
import plotly
import plotly.plotly as py
import pandas as pd
import numpy as np
import time

init_notebook_mode(connected=True)

ourData = pd.read_csv('C:\\Users\\Phil\\Documents\\InteractiveVisuals\\VforInteractiveGraph2.csv')

ourData.head()
Out[20]:
Alpha Epsilon V
0 1.0 0.0 8.00
1 1.0 1.0 13.50
2 1.0 2.0 13.50
3 1.0 3.0 18.25
4 1.0 4.0 18.25

Now, we are going to take the data we have a ‘mash’ it into the plot.ly example. Because our data is somewhat simpler we first negate some of the features used in the example. In theory, you could tidy up the code to remove them but this takes much more time and this method is much simpler.

Therefore, we add two columns to our data with a dummy value of ‘Test1’ and ‘Test2’ for each row. We then add a third column to correspond to the population feature used. As mentioned before, this value corresponds to the size of the points and I have chosen a value that provides suitably sized points but you can edit this if needed.

I then rename all my columns to correspond exactly to the naming convention from the example.

In [21]:
ourData['continent'] = 'Test'
ourData['country'] = 'Test2'
ourData['pop'] = 7000000.0



ourData.columns = ['year', 'lifeExp', 'gdpPercap', 'continent', 'country', 'pop']

ourData.head()
Out[21]:
year lifeExp gdpPercap continent country pop
0 1.0 0.0 8.00 Test Test2 7000000.0
1 1.0 1.0 13.50 Test Test2 7000000.0
2 1.0 2.0 13.50 Test Test2 7000000.0
3 1.0 3.0 18.25 Test Test2 7000000.0
4 1.0 4.0 18.25 Test Test2 7000000.0

The next step is a little tricky, in the example they manually put in each year using the following code:

years = ['1952', '1962', '1967', '1972', '1977', '1982', '1987', '1992', '1997', '2002', '2007']

Therfore, instead of having to manually input this each time, I simply find each unique Alpha value, round and sort in decreasing order and then produce an array of unique Alpha values. I then forced the variable name into ‘Years’ to correspond with the later code.

In [24]:
alpha = list(set(ourData['year']))
alpha = np.round(alpha,1)
alpha = np.sort(alpha)[::-1]
years = np.round([(alpha) for alpha in alpha],1)
years
Out[24]:
array([ 1. ,  0.9,  0.8,  0.7,  0.6,  0.5,  0.4,  0.3,  0.2,  0.1])

Lastly we rename our data to that which correpsonds to the example code and run the whole thing to produce our animated and interactive plot for parameter optimisation.

In the layout section, I have added a main title and re-worded some of the axis labels to correspond to my requirements but it is otherwise unchanged.

In [27]:
dataset = ourData

continents = []
for continent in dataset['continent']:
    if continent not in continents:
        continents.append(continent)
# make figure
figure = {
    'data': [],
    'layout': {},
    'frames': []
}

# fill in most of layout
figure['layout']['title'] = "Parameter Optimisation using Interactive Animation <br> PhilipOsborneData.com"
figure['layout']['xaxis'] = {'range': [1, 100], 'title': 'Epsilon'}
figure['layout']['yaxis'] = {'range': [1,50],'title': 'Sum of V', 'type': 'linear'}
figure['layout']['hovermode'] = 'closest'
figure['layout']['sliders'] = {
    'args': [
        'transition', {
            'duration': 400,
            'easing': 'cubic-in-out'
        }
    ],
    'initialValue': '1952',
    'plotlycommand': 'animate',
    'values': years,
    'visible': True
}
figure['layout']['updatemenus'] = [
    {
        'buttons': [
            {
                'args': [None, {'frame': {'duration': 500, 'redraw': False},
                         'fromcurrent': True, 'transition': {'duration': 300, 'easing': 'quadratic-in-out'}}],
                'label': 'Play',
                'method': 'animate'
            },
            {
                'args': [[None], {'frame': {'duration': 0, 'redraw': False}, 'mode': 'immediate',
                'transition': {'duration': 0}}],
                'label': 'Pause',
                'method': 'animate'
            }
        ],
        'direction': 'left',
        'pad': {'r': 10, 't': 87},
        'showactive': False,
        'type': 'buttons',
        'x': 0.1,
        'xanchor': 'right',
        'y': 0,
        'yanchor': 'top'
    }
]

sliders_dict = {
    'active': 0,
    'yanchor': 'top',
    'xanchor': 'left',
    'currentvalue': {
        'font': {'size': 20},
        'prefix': 'Alpha: ',
        'visible': True,
        'xanchor': 'right'
    },
    'transition': {'duration': 300, 'easing': 'cubic-in-out'},
    'pad': {'b': 10, 't': 50},
    'len': 0.9,
    'x': 0.1,
    'y': 0,
    'steps': []
}

# make data
year = 1.0
for continent in continents:
    dataset_by_year = dataset[np.round(dataset['year'],1) == np.round(year,1)]
    dataset_by_year_and_cont = dataset_by_year[dataset_by_year['continent'] == continent]

    data_dict = {
        'x': list(dataset_by_year_and_cont['lifeExp']),
        'y': list(dataset_by_year_and_cont['gdpPercap']),
        'mode': 'markers',
        'text': list(dataset_by_year_and_cont['country']),
        'marker': {
            'sizemode': 'area',
            'sizeref': 200000,
            'size': list(dataset_by_year_and_cont['pop'])
        },
        'name': continent
    }
    figure['data'].append(data_dict)

# make frames
for year in years:
    frame = {'data': [], 'name': str(year)}
    for continent in continents:
        dataset_by_year = dataset[np.round(dataset['year'],1) == np.round(year,1)]
        dataset_by_year_and_cont = dataset_by_year[dataset_by_year['continent'] == continent]

        data_dict = {
            'x': list(dataset_by_year_and_cont['lifeExp']),
            'y': list(dataset_by_year_and_cont['gdpPercap']),
            'mode': 'markers',
            'text': list(dataset_by_year_and_cont['country']),
            'marker': {
                'sizemode': 'area',
                'sizeref': 200000,
                'size': list(dataset_by_year_and_cont['pop'])
            },
            'name': continent
        }
        frame['data'].append(data_dict)

    figure['frames'].append(frame)
    slider_step = {'args': [
        [year],
        {'frame': {'duration': 300, 'redraw': False},
         'mode': 'immediate',
       'transition': {'duration': 300}}
     ],
     'label': year,
     'method': 'animate'}
    sliders_dict['steps'].append(slider_step)


figure['layout']['sliders'] = [sliders_dict]

iplot(figure)

 

 

Leave a Reply