danpl
danpl

Reputation: 191

Plotly: Scatter plot with dropdown menu to change data and calculated annotation

I'm trying to make a scatter plot with 2 dropdown menus that select a data column (from a pandas data frame) to be plotted for x and y-axis. I also want the plot to have a correlation stats annotation that change with the dropdown selection, because the annotation is calculated on both the x and y data as parameters. The first part I've managed to do with the code example below, but I am struggling with the annotation.

import pandas as pd
import numpy as np
import plotly.graph_objects as go

# Prep random data
data = pd.DataFrame(dict(
    A=np.random.randint(11, size=10),
    B=np.random.randint(11, size=10),
    C=np.random.randint(11, size=10),
    D=np.random.randint(11, size=10) 
))

# Create figure and add one scatter trace
fig = go.Figure()

fig.add_trace(go.Scatter(
    x=data['A'], 
    y=data['A'],
    visible=True,
    mode='markers',
    )
              )

# Create x and y buttons
x_buttons = []
y_buttons = []

for column in data.columns:
    x_buttons.append(dict(method='restyle',
                        label=column,
                        args=[{'x': [data[column]]}]
                        )
                )
    
    y_buttons.append(dict(method='restyle',
                        label=column,
                        args=[{'y': [data[column]]}]
                        )
                )

# Pass buttons to the updatemenus argument
fig.update_layout(updatemenus=[dict(buttons=x_buttons, direction='up', x=0.5, y=-0.1),
                               dict(buttons=y_buttons, direction='right', x=-0.01, y=0.5)])

My idea was to first define a function that will take the x and y attributes from the figure data structure (hoping that the dropdown selection change this attributes) and returns the text annotation. Then, based on the plotly reference example, add the annotation to args and change the method of the buttons to 'update'. However, that doesn't seem to be the case and the annotation is static. Anyone has an idea of how I could achieve this? Here is the function and the final code:

from scipy import stats

def corr_annotation(x, y):
    pearsonr = stats.pearsonr(x, y)
    return 'r = {:.2f} (p = {:.3f})'.format(pearsonr[0], pearsonr[1])
import pandas as pd
import numpy as np
import plotly.graph_objects as go

# Prep random data
data = pd.DataFrame(dict(
    A=np.random.randint(11, size=10),
    B=np.random.randint(11, size=10),
    C=np.random.randint(11, size=10),
    D=np.random.randint(11, size=10) 
))

# Create figure and add one scatter trace
fig = go.Figure()

fig.add_trace(go.Scatter(
    x=data['A'], 
    y=data['A'],
    visible=True,
    mode='markers',
    )
              )

fig.add_annotation(dict(text=corr_annotation(fig['data'][0]['x'], fig['data'][0]['y']),
                        showarrow=False, 
                        yref='paper', xref='paper',
                        x=0.99, y=0.95))

# Create x and y buttons
x_buttons = []
y_buttons = []

for column in data.columns:
    x_buttons.append(dict(method='update',
                        label=column,
                        args=[{'x': [data[column]]},
                              {'annotations': [dict(text=corr_annotation(fig['data'][0]['x'], fig['data'][0]['y']),
                                                    showarrow=False, 
                                                    yref='paper', xref='paper',
                                                    x=0.99, y=0.95)]}]
                        )
                )
    
    y_buttons.append(dict(method='update',
                        label=column,
                        args=[{'y': [data[column]]},
                              {'annotations': [dict(text=corr_annotation(fig['data'][0]['x'], fig['data'][0]['y']),
                                                    showarrow=False, 
                                                    yref='paper', xref='paper',
                                                    x=0.99, y=0.95)]}]
                        )
                )

# Pass buttons to the updatemenus argument
fig.update_layout(updatemenus=[dict(buttons=x_buttons, direction='up', x=0.5, y=-0.1),
                               dict(buttons=y_buttons, direction='right', x=-0.01, y=0.5)])

And the final result

Upvotes: 1

Views: 5390

Answers (2)

danpl
danpl

Reputation: 191

The solution for me was to change to a single dropdown button that select pairs of variables (i.e. changes both x and y). One caveat to this is when dealing with large datasets, as the number of combinations can get pretty big, but for my case (~20 columns) it was fine.

from scipy import stats

def corr_annotation(x, y):
    pearsonr = stats.pearsonr(x, y)
    return 'r = {:.2f} (p = {:.3f})'.format(pearsonr[0], pearsonr[1])

# Prep random data
import pandas as pd
import numpy as np

np.random.seed(12)

data = pd.DataFrame(dict(
    A=np.random.randint(11, size=10),
    B=np.random.randint(11, size=10),
    C=np.random.randint(11, size=10),
    D=np.random.randint(11, size=10) 
))

# Create base figure
import plotly.express as px

fig = px.scatter(data, x='A', y='B')

fig.add_annotation(dict(text=corr_annotation(data['A'], data['B']),
                        showarrow=False, 
                        yref='paper', xref='paper',
                        x=0.99, y=0.95))

# Create buttons
import itertools

buttons = []

for x, y in itertools.combinations(data.columns, 2):
    buttons.append(dict(method='update',
                        label='{} x {}'.format(x, y),
                        args=[{'x': [data[x]],
                               'y': [data[y]]},
                              {'xaxis': {'title': x},
                               'yaxis': {'title': y},
                               'annotations': [dict(text=corr_annotation(data[x], data[y]),
                                                    showarrow=False, 
                                                    yref='paper', xref='paper',
                                                    x=0.99, y=0.95)]}]
                        )
                   )

# Update and show figure
fig.update_layout(updatemenus=[dict(buttons=buttons, direction='down', x=0.1, y=1.15)])

fig.show()

Upvotes: 4

r-beginners
r-beginners

Reputation: 35155

Since we need to create annotations for each of them, we will create annotations for the x-axis and for the y-axis for the x,y combinations in ABCD order and DCBA order. We have the same R-values, but we have not verified them, so please deal with them yourself.

from scipy import stats

def corr_annotation(x, y):
    pearsonr = stats.pearsonr(x, y)
    return 'r = {:.2f} (p = {:.3f})'.format(pearsonr[0], pearsonr[1])

import pandas as pd
import numpy as np
import plotly.graph_objects as go

# Prep random data
data = pd.DataFrame(dict(
    A=np.random.randint(11, size=10),
    B=np.random.randint(11, size=10),
    C=np.random.randint(11, size=10),
    D=np.random.randint(11, size=10) 
))

# Create figure and add one scatter trace
fig = go.Figure()

fig.add_trace(go.Scatter(
    x=data['A'], 
    y=data['A'],
    visible=True,
    mode='markers',
    )
              )

fig.add_annotation(dict(text=corr_annotation(fig['data'][0]['x'], fig['data'][0]['y']),
                        showarrow=False, 
                        yref='paper', xref='paper',
                        x=0.99, y=0.95))

# Create x and y buttons
x_buttons = []
y_buttons = []

for ncol,rcol in zip(data.columns, data.columns[::-1]):
    x_buttons.append(dict(method='update',
                        label=ncol,
                        args=[{'x': [data[ncol]]},
                              {'annotations': [dict(text=corr_annotation(data[ncol], data[rcol]),
                                                    showarrow=False, 
                                                    yref='paper', xref='paper',
                                                    x=0.99, y=0.95)]}]
                        )
                )
    
    y_buttons.append(dict(method='update',
                        label=ncol,
                        args=[{'y': [data[ncol]]},
                              {'annotations': [dict(text=corr_annotation(data[rcol], data[ncol]),
                                                    showarrow=False, 
                                                    yref='paper', xref='paper',
                                                    x=0.99, y=0.95)]}]
                        )
                )

# Pass buttons to the updatemenus argument
fig.update_layout(updatemenus=[dict(buttons=x_buttons, direction='up', x=0.5, y=-0.1),
                               dict(buttons=y_buttons, direction='right', x=-0.01, y=0.5)])

fig.show()

enter image description here

Upvotes: 2

Related Questions