Search code examples
pythonplotlyvisualizationipywidgets

Interactive plotly boxplot with ipywidgets


I am trying to create an interactive boxplot with ipywidgets and Plotly.

I started by looking at this example

While this is fine, I'd like to change the groupings of the boxplot based on a dropdown input.

With interact I can do this:

import datetime
import numpy as np
import pandas as pd

import plotly.graph_objects as go
from ipywidgets import widgets

df = pd.read_csv(
 'https://raw.githubusercontent.com/yankev/testing/master/datasets/nycflights.csv')
df = df.drop(df.columns[[0]], axis=1)

from ipywidgets import interact

def view_image(col):
    fig = go.FigureWidget()
    for val in df[col].unique():
        groupData = df.query(f'{col} == "{val}"')
        fig.add_trace(
            go.Box(y = groupData['distance'],
                  name = val)
        )
    fig.show()
    

    
interact(view_image, col = ['origin', 'carrier'])

And the result is that I can change the column based on which the data is grouped.

However, I would like to have more control on the widgets, like in the official example.

This is what I am trying (and failing):

# Assign an empty figure widget with two traces
gdata = []
for origin in df.origin.unique():
    groupData = df.query(f'origin == "{origin}"')
    gdata.append(
        go.Box(y = groupData['distance'],
              name = origin)
    )
g = go.FigureWidget(data=gdata,
                    layout=go.Layout(
                        title=dict(
                            text='NYC FlightDatabase'
                        ),
                        barmode='overlay'
                    ))

def response_box(change):
    col = column.value
    with g.batch_update():
        gdata = []
        for val in df[col].unique():
            groupData = df.query(f'{col} == "{val}"')
            gdata.append(
                go.Box(y = groupData['distance'],
                      name = val)
            )
        g.data = gdata

column = widgets.Dropdown(
    options=['origin','carrier']
)
column.observe(response_box, 'value')
container2 = widgets.HBox([column])
widgets.VBox([container2,
              g])

Note that since I have new groupings, I cannot just go into g.data[index].y and change per index, but I have to re-generate the figure as in the interact function.

This particular iteration gives me a "you cannot update data directly" error. I tried in a few different ways, but I don't seem to find one that works.

Any idea?


Solution

    • it's not clear how you want to interact with the dimensions of data. So I've gone with defining x and color of figure, plus filtering by origin, dest, carrier
    • box plots are far simpler to create using Plotly Express so have used that
    • it then really simplifies to passing parameters. Have used https://ipywidgets.readthedocs.io/en/latest/examples/Using%20Interact.html with decorator
    import datetime
    import numpy as np
    import pandas as pd
    
    import plotly.graph_objects as go
    import plotly.express as px
    from ipywidgets import widgets
    from ipywidgets import interact
    
    df = pd.read_csv(
        "https://raw.githubusercontent.com/yankev/testing/master/datasets/nycflights.csv"
    )
    df = df.drop(df.columns[[0]], axis=1)
    
    @interact
    def view_image(
        col=widgets.Dropdown(
            description="Plot:", value="carrier", options=["origin", "carrier"]
        ),
        filtercol=widgets.Dropdown(
            description="Filter by:", value="carrier", options=["origin", "dest", "carrier"]
        ),
    
        filter=widgets.Text(
            description="Filter:", value=""
        ),
    ):
        # check if filter results in any rows... if not all data...
        if df[filtercol].eq(filter).any():
            dfp = df.loc[df[filtercol].eq(filter)]
        else:
            dfp = df
        fig = px.box(dfp, x=col, y="distance", color=col)
        go.FigureWidget(fig.to_dict()).show()
    

    enter image description here