Plotly Fundamentals -
Controls

In this chapter we will add custom controls to our charts which can turn your plots into simple dashboards. To start we will once again import Pandas as well as both the Plotly express and Plotly graph_objects packages.

import pandas as pd
import plotly.express as px
import plotly.graph_objects as go

As sample data we will use the iris data set provided by Plotly express which we can inspect visually using the Pandas function head().

iris = px.data.iris()
iris.head()
sepal_length sepal_width petal_length petal_width species species_id
0 5.1 3.5 1.4 0.2 setosa 1
1 4.9 3.0 1.4 0.2 setosa 1
2 4.7 3.2 1.3 0.2 setosa 1
3 4.6 3.1 1.5 0.2 setosa 1
4 5.0 3.6 1.4 0.2 setosa 1

Before we will dig into the actual controls we shortly revisit what we have learned about the Plotly graph objects data type. To do that, we are creating a simple scatter chart with plotly graph objects ans subsequently print the figure. The result is a dictionary style structure which contains all information plotly needs to interpret how the chart should look like. The methods we will get to know are designed to make direct 

fig = go.Figure(data = [go.Scatter(x= iris['sepal_length'], y = iris['sepal_width'], mode = 'markers', marker=dict(color = 'blue'))],
               layout = go.Layout(height = 800, plot_bgcolor = 'white')
               )
fig.show()
print(fig)

The result is a dictionary style structure that contains all information plotly needs to interpret how the chart should look like. When adding controls to your charts we will add, change or replace attributes in the figure tree. Therefore it is always useful to inspect the tree for information if you do not know exactly how to tackle a certain transformation you might want to implement with a button, slider or other control element.

Figure({
    'data': [{'marker': {'color': 'blue'},
              'mode': 'markers',
              'type': 'scatter',
              'x': array([5.1, 4.9, 4.7, 4.6, 5. , 5.4, 4.6, 5. , 4.4, 4.9, 5.4, 4.8, 4.8, 4.3,
                          5.8, 5.7, 5.4, 5.1, 5.7, 5.1, 5.4, 5.1, 4.6, 5.1, 4.8, 5. , 5. , 5.2,
                          5.2, 4.7, 4.8, 5.4, 5.2, 5.5, 4.9, 5. , 5.5, 4.9, 4.4, 5.1, 5. , 4.5,
                          4.4, 5. , 5.1, 4.8, 5.1, 4.6, 5.3, 5. , 7. , 6.4, 6.9, 5.5, 6.5, 5.7,
                          6.3, 4.9, 6.6, 5.2, 5. , 5.9, 6. , 6.1, 5.6, 6.7, 5.6, 5.8, 6.2, 5.6,
                          5.9, 6.1, 6.3, 6.1, 6.4, 6.6, 6.8, 6.7, 6. , 5.7, 5.5, 5.5, 5.8, 6. ,
                          5.4, 6. , 6.7, 6.3, 5.6, 5.5, 5.5, 6.1, 5.8, 5. , 5.6, 5.7, 5.7, 6.2,
                          5.1, 5.7, 6.3, 5.8, 7.1, 6.3, 6.5, 7.6, 4.9, 7.3, 6.7, 7.2, 6.5, 6.4,
                          6.8, 5.7, 5.8, 6.4, 6.5, 7.7, 7.7, 6. , 6.9, 5.6, 7.7, 6.3, 6.7, 7.2,
                          6.2, 6.1, 6.4, 7.2, 7.4, 7.9, 6.4, 6.3, 6.1, 7.7, 6.3, 6.4, 6. , 6.9,
                          6.7, 6.9, 5.8, 6.8, 6.7, 6.7, 6.3, 6.5, 6.2, 5.9]),
              'y': array([3.5, 3. , 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1, 3.7, 3.4, 3. , 3. ,
                          4. , 4.4, 3.9, 3.5, 3.8, 3.8, 3.4, 3.7, 3.6, 3.3, 3.4, 3. , 3.4, 3.5,
                          3.4, 3.2, 3.1, 3.4, 4.1, 4.2, 3.1, 3.2, 3.5, 3.1, 3. , 3.4, 3.5, 2.3,
                          3.2, 3.5, 3.8, 3. , 3.8, 3.2, 3.7, 3.3, 3.2, 3.2, 3.1, 2.3, 2.8, 2.8,
                          3.3, 2.4, 2.9, 2.7, 2. , 3. , 2.2, 2.9, 2.9, 3.1, 3. , 2.7, 2.2, 2.5,
                          3.2, 2.8, 2.5, 2.8, 2.9, 3. , 2.8, 3. , 2.9, 2.6, 2.4, 2.4, 2.7, 2.7,
                          3. , 3.4, 3.1, 2.3, 3. , 2.5, 2.6, 3. , 2.6, 2.3, 2.7, 3. , 2.9, 2.9,
                          2.5, 2.8, 3.3, 2.7, 3. , 2.9, 3. , 3. , 2.5, 2.9, 2.5, 3.6, 3.2, 2.7,
                          3. , 2.5, 2.8, 3.2, 3. , 3.8, 2.6, 2.2, 3.2, 2.8, 2.8, 2.7, 3.3, 3.2,
                          2.8, 3. , 2.8, 3. , 2.8, 3.8, 2.8, 2.8, 2.6, 3. , 3.4, 3.1, 3. , 3.1,
                          3.1, 3.1, 2.7, 3.2, 3.3, 3. , 2.5, 3. , 3.4, 3. ])}],
    'layout': {'height': 800, 'plot_bgcolor': 'white', 'template': '...'}

We will now use this chart and add two buttons with the update_layout function. We will pass a nested dictionary to the updatemenus input parameter that specifies the look and behaviour of our buttons. As usual, there are a ton of styling options to choose from and you can play around with them to make the chart look sleek. The core building block of the update menu is the buttons argument though. It contains a list of dictionaries with each entry representing one button. Inside you have to at least pass values to the args parameter and to the method parameter. The args parameter once again takes a list with two entries. The first refers to the element in the figure tree you would like to change when you press the button and the second contains a dictionary that specifies how you want the first element to change. The method parameter refers to the main branches of the figure tree that you would like to change. In case you want to change the layout you need to pass ‘relayout’, in case you want to change the figure data you use ‘restyle’ and if you want to change both you use ‘update’. There is a fourth option in ‘animate’ that we will explore in the next chapter.

In the example below we will add two buttons that change the way our markers are displayed. Since markers are part of the data branch of the figure tree we need to pass ‘restyle’ to the methods parameter.

fig.update_layout(updatemenus=[
        dict(
            type = "buttons",
            direction = "left",
            buttons=list([
                dict(
                    args=["marker", dict(color = 'blue')],
                    label="classic marker",
                    method="restyle"
                ),
                dict(
                    args=["marker", dict(color = 'red', size=12, line=dict(width=2, color='DarkSlateGrey'))],
                    label="fancy marker",
                    method="restyle"
                )
            ]),
            pad={"r": 10, "t": 10},
            showactive=True,
            x=0.01,
            xanchor="left",
            y=1,
            yanchor="top"
        ),
    ])
fig.show()

To demonstrate how to change the layout we need to pass ‘relayout’ to the method parameter and change the args value to a section of the layout tree such as the background color accessible via ‘plot_bgcolor’.

fig.update_layout(updatemenus=[
        dict(
            type = "buttons",
            direction = "left",
            buttons=list([
                dict(
                    args=["plot_bgcolor", 'white'],
                    label="white background",
                    method="relayout"
                ),
                dict(
                    args=["plot_bgcolor", '#E4DFCF'],
                    label="brown background",
                    method="relayout"
                )
            ]),
            pad={"r": 10, "t": 10},
            showactive=True,
            x=0.01,
            xanchor="left",
            y=1,
            yanchor="top"
        ),
    ])
fig.show()

When you add too many buttons the figure becomes quite busy. To work around that it might make sense to group buttons that do similar things into a dropdown menu. To do that you can simply change the type parameter to ‘dropdown’ and keep everything else in place.

fig.update_layout(updatemenus=[
        dict(
            type = 'dropdown',
            direction = "down",
            buttons=list([
                dict(
                    args=["marker", dict(color = 'blue')],
                    label="classic marker",
                    method="restyle"
                ),
                dict(
                    args=["marker", dict(color = 'red', size=12, line=dict(width=2, color='DarkSlateGrey'))],
                    label="fancy marker",
                    method="restyle"
                )
            ]),
            pad={"r": 10, "t": 10},
            showactive=True,
            x=0.01,
            xanchor="left",
            y=1,
            yanchor="top"
        ),
    ])
fig.show()

The final type of control element we will look at is the slider. The iris dataset is rather static so we will import the gapminder dataset as it contains yearly data which is more natural to be transformed using a slider.

gap = px.data.gapminder()
gap.head()
country continent year lifeExp pop gdpPercap iso_alpha iso_num
0 Afghanistan Asia 1952 28.801 8425333 779.445314 AFG 4
1 Afghanistan Asia 1957 30.332 9240934 820.853030 AFG 4
2 Afghanistan Asia 1962 31.997 10267083 853.100710 AFG 4
3 Afghanistan Asia 1967 34.020 11537966 836.197138 AFG 4
4 Afghanistan Asia 1972 36.088 13079460 739.981106 AFG 4

The easiest way to include a slider is by using Plotly express. You can add a column name to the optional parameters animation_frame and animation_group. The first specifies what values your slider displays and the second is needed to have your data points displayed consistently throughout transition from one slider step to the next. You probably noticed that the inputs mention animations and in fact you will not only get a slider but also a play and pause button which automatically triggers stepwise transition through the slider values. Since we will have a deeper look into animations in the next chapter we can remove the buttons by removing the ‘updatemenus’ dictionary from the figure tree using the pop function.

fig = px.scatter(gap, x="gdpPercap", y="lifeExp", animation_frame="year", animation_group="country",
           size="pop", color="continent", hover_name="country",
           log_x=True, size_max=55, range_x=[100,100000], range_y=[25,90])

fig["layout"].pop("updatemenus") # optional, drop animation buttons
fig.show()

Obviously, using Plotly express is rather convenient and does not require much code. However it is also very inflexible and you might want to customise your slider further. The example below shows how to achieve a similar effect with plotly graph objects even though it is stripped of much of the configuration to make the code not too confusing. That can be added rather easily though.

The slider chart is set up in four steps. First, we create a mostly empty figure. Second, we add a trace for every year in the dataframe and at the same time turn off visibility except for the first one. When adding the slider each transition of the little knob on the timeline will make a different trace visible. For that we need to iterate through the list of traces stored in the figures data attribute to build up a list of dictionaries called steps. Those work similar to the button dictionaries we have already encountered. The need a method value (in this case ‘update’) and input for the args argument which in this case turns on visibility for each individual trace. In the fourth and final step we wrap the list of steps dictionaries in a sliders list and pass this object to the update_layout method as input to the corresponding parameter. 

#1) create figure

fig = go.Figure(layout = go.Layout(height = 800, showlegend = False))


#2) add one trace for each year and make them invisible except for first one
years = list(gap['year'].unique())

for year in years:
    fig.add_trace(go.Scatter(x=gap[gap['year']==year]['gdpPercap'],y=gap[gap['year']==year]['lifeExp'], 
                             mode = 'markers', marker = dict(color = 'blue'), visible = False))
fig.data[0].visible = True


#3) iterate through number of created traces to create dictionary with to be updated attribute of each trace
#each step makes only one year visible

steps = []
for i in range(len(fig.data)):
    step = dict(
        method="update",
        label=str(years[i]),
        args=[{"visible": [False] * len(fig.data)}],  # layout attribute
    )
    step["args"][0]["visible"][i] = True  # Toggle i'th trace to "visible"
    steps.append(step)

#4)prepare slider element passing steps as key input

sliders = [dict(
    #active=10,
    steps=steps
)]


fig.update_layout(
    sliders=sliders
)

    
fig.update_xaxes(type="log")
fig.show()

We made it to the end of the sixth chapter of our Plotly tutorial. Good job! The only thing left to do is fully animate our plots which creates some of the most stunning visuals the Plotly library has to offer with relatively little effort. See you there…

fistofgeek.com

coding - data science - finance

Get Connected