1

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

2 Answers 2

4

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()
Sign up to request clarification or add additional context in comments.

2 Comments

I thought it would be best to make it a combination button instead of each button as well, since I tested it again after receiving your comment and it didn't solve the problem. Please vote +1 if my answer helps you solve the problem!
For my specific case it was better after all, but I think that if there is a solution with independent buttons it would be easier to use and (with large datasets) less cluttered. It helped to get to this idea, I'm just waiting the reputation score to be able to do it, thanks!
2

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

2 Comments

To check the layout structure of the graph you have created, use fig.layout. The layout structure will be displayed in a dictionary format so that you can check the contents. If my answer is helpful to you, there is a check mark under the reputation up/down button, click on it to accept the answer.
Thanks for answering! Works for [(A,D), (D,A)] and [(B,C), (C,B] pairs, depending on how I select the buttons, but the other pairs are lefted out. The problem to me is that the buttons are independent, but the annotation depends on both x and y, so it's hard to think of a solution without being able to access the "active" data or state of the buttons. Maybe change to 1 dropdown that select pairs of variables instead of a single one? I'll try that and update the post if it works.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.