1

I am trying to make a plotly scatter plot with a OLS trendline and provide a dropdown menu what let's the user pick from a different of X's and Y's. The plot almost works. The code below updates the data but does not update the X and Y axes titles or the labels for the data. So when you hover over a given point, it always show the X variables as CRTTOTAL and Y variable as AVG_MISPERCEPTION.

The code:


    import plotly.graph_objects as go
    import plotly.express as px
    #making a figure
    fig = go.Figure()
    x1 = df['crttotal']
    x2 = df['nfcc_mean']
    x3 = df['bficonmean']
    x4 = df['bfiopmean']
    
    y1 = df['avg_misperception_score']
    y2 = df['avg_ambiguous_score']
    
    
    
    # make figure
    fig = px.scatter(df, x=x1, y=y1, trendline="ols", trendline_scope="overall", labels = {"crttotal":"crttotal", "avg_misperception_score":"avg_misperception_score"})
    
    
    #making the dropdown
    fig.update_layout(
        updatemenus=[
            go.layout.Updatemenu(
                type="dropdown",
                buttons=list([
                    
                    dict(label="CRT vs Misperception",
                         method="update",
                         args=[{"x": [x1, px.scatter(x=x1, y=y1, trendline='ols').data[1].x], 
                                "y": [y1, px.scatter(x=x1, y=y1, trendline='ols').data[1].y], 
                                "trendline":["ols"], 
                                "trendline_scope":["overall"]},
                               {"title": "CRT vs Misperception"}]),
                    
                    dict(label="NFCC vs Misperception",
                         method="update",
                         args=[{"x": [x2, px.scatter(x=x2, y=y1, trendline='ols').data[1].x], 
                                "y": [y1, px.scatter(x=x2, y=y1, trendline='ols').data[1].y], 
                                "trendline":["ols"], 
                                "trendline_scope":["overall"]},
                               {"title": "NFCC vs Misperception"}]),
                    
                    dict(label="bficonmean vs Misperception",
                         method="update",
                         args=[{"x": [x3, px.scatter(x=x3, y=y1, trendline='ols').data[1].x], 
                                "y": [y1, px.scatter(x=x3, y=y1, trendline='ols').data[1].y], 
                                "trendline":["ols"], "trendline_scope":["overall"]},
                               {"title": "bficonmean vs Misperception"}]),
                    
                    dict(label="bfiopmean vs Misperception",
                         method="update",
                         args=[{"x": [x4, px.scatter(x=x4, y=y1, trendline='ols').data[1].x], 
                                "y": [y1, px.scatter(x=x4, y=y1, trendline='ols').data[1].y],
                                "trendline":["ols"], 
                                "trendline_scope":["overall"]},
                               {"title": "bfiopmean vs Misperception"}]),
    
                    ### changing the y variable also
                    dict(label="CRT vs Ambiguity",
                         method="update",
                         args=[{"x": [x1, px.scatter(x=x1, y=y2, trendline='ols').data[1].x], 
                                "y": [y2, px.scatter(x=x1, y=y2, trendline='ols').data[1].y], 
                                "trendline":["ols"],
                                "trendline_scope":["overall"]},
                               {"title": "CRT vs Ambiguity"}]),
                    
                    dict(label="NFCC vs Ambiguity",
                         method="update",
                         args=[{"x": [x2, px.scatter(x=x2, y=y2, trendline='ols').data[1].x],
                                "y": [y2, px.scatter(x=x2, y=y2, trendline='ols').data[1].y], 
                                "trendline":["ols"],
                                "trendline_scope":["overall"]},
                               {"title": "NFCC vs Ambiguity"}]),
                    
                    dict(label="bficonmean vs Ambiguity",
                         method="update",
                         args=[{"x": [x3, px.scatter(x=x3, y=y2, trendline='ols').data[1].x], 
                                "y": [y2, px.scatter(x=x3, y=y2, trendline='ols').data[1].y], 
                                "trendline":["ols"], 
                                "trendline_scope":["overall"]},
                               {"title": "bficonmean vs Ambiguity"}]),
                    
                    dict(label="bfiopmean vs Ambiguity",
                         method="update",
                         args=[{"x": [x4, px.scatter(x=x4, y=y2, trendline='ols').data[1].x], 
                                "y": [y2, px.scatter(x=x4, y=y2, trendline='ols').data[1].y],
                                "trendline":["ols"], 
                                "trendline_scope":["overall"]},
                               {"title": "bfiopmean vs Ambiguity"}])
    
                ])
            )
        ]
    )
    
    #set the title
    fig.update_layout(title="Dropdown")
    
    fig.show()

The Data


       crttotal  nfcc_mean  bficonmean  bfiopmean  avg_misperception_score  \
    0         3       2.87       3.875      3.000                   -0.062   
    1         0       3.53       3.625      3.125                   -0.235   
    2         0       3.80       4.000      3.000                    0.077   
    3         0       3.73       3.750      3.500                    0.067   
    4         2       3.87       3.125      3.000                    0.368   
    5         0       3.47       2.750      3.500                   -0.200   
    6         0       4.33       3.625      3.625                   -0.200   
    7         0       4.13       3.250      3.125                   -0.500   
    8         0       4.73       3.250      3.250                   -0.643   
    9         3       5.20       3.750      2.750                    0.000   
    
       avg_ambiguous_score  
    0                 2.60  
    1                 2.10  
    2                 3.35  
    3                 2.55  
    4                 2.90  
    5                 2.80  
    6                 2.85  
    7                 3.30  
    8                 3.15  
    9                 2.70  

** What I've tried** Adding arguments to the buttons so that it updates the px.scatter funtion:


    fig.update_layout(
        updatemenus=[
            go.layout.Updatemenu(
                type="dropdown",
                buttons=list([
                    
                    dict(label="CRT vs Misperception", # button label
                         method="update",
                         args=[{"x": [x1, px.scatter(x=x1, y=y1, trendline='ols').data[1].x], 
                                "y": [y1, px.scatter(x=x1, y=y1, trendline='ols').data[1].y], 
                                "trendline":["ols"], 
                                "trendline_scope":["overall"],
                            **  "labels":{"crttotal":"CRT",
                            **  "avg_misperception_score":"Misperception"}},
                               {"title": "CRT vs Misperception"}]),
                    
                    dict(label="NFCC vs Misperception",
                         method="update",
                         args=[{"x": [x2, px.scatter(x=x2, y=y1, trendline='ols').data[1].x], 
                                "y": [y1, px.scatter(x=x2, y=y1, trendline='ols').data[1].y], 
                                "trendline":["ols"], 
                                "trendline_scope":["overall"],
                                },
                               {"title": "NFCC vs Misperception",
                               ... ETC. 

MartyMcFly
  • 63
  • 6

2 Answers2

1

If you use dash, you can refer below code:

from dash import html
import dash_bootstrap_components as dbc
from dash import dcc
from dash import html,callback
from dash.dependencies import Input, Output
import plotly.express as px
import pandas as pd
import dash

df = pd.read_csv('Data_test.csv')
x1 = df['crttotal']
x2 = df['nfcc_mean']
x3 = df['bficonmean']
x4 = df['bfiopmean']
    
y1 = df['avg_misperception_score']
y2 = df['avg_ambiguous_score']

app = dash.Dash(__name__)

app.layout = html.Div([
    dbc.Row([
        dbc.Col([
            html.H5('Dropdown',className='text-center'),
            dcc.Dropdown(id='selection',placeholder="Please select dropdown",
                         options=[{'label':'CRT vs Misperception','value':'CRT vs Misperception'},
                                  {'label':'NFCC vs Misperception','value':'NFCC vs Misperception'},
                                  {'label':'bficonmean vs Misperception','value':'bficonmean vs Misperception'},
                                  {'label':'bfiopmean vs Misperception','value':'bfiopmean vs Misperception'},
                                  {'label':'CRT vs Ambiguity','value':'CRT vs Ambiguity'},
                                  {'label':'NFCC vs Ambiguity','value':'NFCC vs Ambiguity'},
                                  {'label':'bficonmean vs Ambiguity','value':'bficonmean vs Ambiguity'},
                                  {'label':'bfiopmean vs Ambiguity','value':'bfiopmean vs Ambiguity'}],
                         value='CRT vs Misperception',
                         multi=False,
                         disabled=False,
                         clearable=False,
                         searchable=True),
        ],width={'size':12,"offset":0,'order':1}),
    ], className='p-2 align-items-stretch'),
    
    dbc.Row([
        dbc.Col([
            dcc.Graph(id="graph",figure={},style={'height':450})
        ],width={'size':12,'offset':0,'order':2}),
    ], className='p-2 align-items-stretch'),
])

@app.callback(Output('graph','figure'), 
              [Input('selection', 'value')])
def data_picker(selection):
    if selection == 'CRT vs Misperception':
        fig = fig = px.scatter(df, x=x1, y=y1, trendline="ols", trendline_scope="overall")
    elif selection == 'NFCC vs Misperception':
        fig = fig = px.scatter(df, x=x2, y=y1, trendline="ols", trendline_scope="overall")
    elif selection == 'bficonmean vs Misperception':
        fig = fig = px.scatter(df, x=x3, y=y1, trendline="ols", trendline_scope="overall")
    elif selection == 'bfiopmean vs Misperception':
        fig = fig = px.scatter(df, x=x4, y=y1, trendline="ols", trendline_scope="overall")        
    elif selection == 'CRT vs Ambiguity':
        fig = fig = px.scatter(df, x=x1, y=y2, trendline="ols", trendline_scope="overall")
    elif selection == 'NFCC vs Ambiguity':
        fig = fig = px.scatter(df, x=x2, y=y2, trendline="ols", trendline_scope="overall")
    elif selection == 'bficonmean vs Ambiguity':
        fig = fig = px.scatter(df, x=x3, y=y2, trendline="ols", trendline_scope="overall")
    elif selection == 'bfiopmean vs Ambiguity':
        fig = fig = px.scatter(df, x=x4, y=y2, trendline="ols", trendline_scope="overall")    
    return fig


if __name__ == '__main__':
    app.run(debug=False)

enter image description here

I'm not sure that dropdown button on graph can change title of axis or not but callback can.

hoa tran
  • 1,391
  • 1
  • 5
  • 14
  • Ty for your help! Follow up question: When I try to run your code it the cell doesn't run and i get a message "Dash is running on http://127.0.0.1:8050/ * Serving Flask app '__main__' * Debug mode: off WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:8050 Press CTRL+C to quit" do you know how to fix that? I don't know dash at all. – MartyMcFly Feb 01 '23 at 23:35
  • @MartyMcFly: just click the link and it will show graph with dropdown like my capture. – hoa tran Feb 02 '23 at 00:45
  • I had tried that, but I get this error in chrome: "This site can’t be reached127.0.0.1 refused to connect." – MartyMcFly Feb 02 '23 at 15:08
  • You have to run code and when link appear, click it. Dont click link here. – hoa tran Feb 03 '23 at 01:01
  • 1
    This worked perfectly! Sorry for the delay - life got me. This solution worked perfectly. The problem was my firewall – MartyMcFly Feb 13 '23 at 01:11
1

I'm regarding this as a follow-up question to How to add a OLS trendline to a plotly scatter plot graph object that uses updatemenus to display subsets of data?. What you were aiming to achieve there turned out to be pretty much impossible if you were to accomplish all the details - such as a correct hovertemplate for each subplot. Getting dynamic and changing X and Y titles would be very much doable though. But my following suggestion, either you're using JupyterDash or Dash, is much easier with the setup in the code below. No updatemenus with complicated args or anything like that, just simply:

fig = px.scatter(df, x=x, y=y, trendline="ols",
                 trendline_scope="overall")

...where values for x and y are determined through dropdowns using dcc.Dropdown in a callback function like this:

@ app.callback(Output('fig1', 'figure'),
               [Input('y_val', 'value'),
               Input('x_val', 'value')])
def trends(y, x):

    fig = px.scatter(df, x=x, y=y, trendline="ols",
                     trendline_scope="overall")
    fig.update_layout(paper_bgcolor='rgba(0,0,0,0)',
                      plot_bgcolor='rgba(0,0,0,0)')
    return fig

Looking at the complete code snippet below, you'll see that there's a bunch of other stuff going on, like defining the structure and layout of the Dash app, but if you take the time to read just a little but more about Dash, you'll quickly find that this is the way to go. Not just with this example, but for all other analytics or data science challenges that should come your way. That's a bold statement on behalf of the Plotly team, but I'll stand by it!

Anyway, here's a gif of the resulting app:

enter image description here

And here's the complete code for a JupyterLab version. If you decide to go with pure Dash, just make the few edits described in Plotly: How to rewrite a standard dash app to launch it in JupyterLab?

from jupyter_dash import JupyterDash
from dash import Dash, html, dcc, Input, Output
import dash_bootstrap_components as dbc

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


# data

df = pd.DataFrame({'crttotal': np.random.random(8),
                   'nfcc_mean': np.random.random(8),
                   'bficonmean': np.random.random(8),
                   'bfiopmean': np.random.random(8),
                   'avg_misperception_score': np.random.random(8),
                   'avg_ambiguous_score': np.random.random(8)})

# app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app = JupyterDash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

y_variables = ['avg_misperception_score', 'avg_ambiguous_score']
x_variables = ['crttotal', 'nfcc_mean', 'bficonmean', 'bfiopmean']
# dd_options = options.remove(target_variable)


app.layout = dbc.Container(
    [
        dbc.Row(
            [
                dbc.Col(
                    [
                        dcc.Markdown(
                            "#### Plotly Express line chart with trendlines and dropdown",
                            className="text-white",

                        )
                    ],  # style={"textalign": "center"},
                    width=8
                )
            ],
            className="bg-secondary rounded-3 mt-2",
            # style={"textalign": "center"},

            justify="center"

        ),
        dbc.Row(

            [

                dbc.Col([dbc.Label(
                    "Target variable:",
                    # className="bg-info bg-opacity-50 mt-2 p-2",
                    className="mt-2 p-2",
                    style={"width": "100%"},
                ),
                    dcc.Dropdown(y_variables,
                                 y_variables[0], id='y_val'),
                    dbc.Label(
                    "Explanatory variable:",
                    # className="bg-info bg-opacity-50 mt-2 p-2",
                    className="mt-2 p-2",
                    style={"width": "100%"},
                ),
                    dcc.Dropdown(x_variables,
                                 x_variables[0], id='x_val'),

                ], width=4),
                dbc.Col(
                    [

                        dcc.Graph(id='fig1')
                    ]
                )
            ],
            className="bg-secondary bg-opacity-25 rounded-3 p-2 mt-2",
        ),
    ]
)


@ app.callback(Output('fig1', 'figure'),
               [Input('y_val', 'value'),
               Input('x_val', 'value')])
def trends(y, x):

    fig = px.scatter(df, x=x, y=y, trendline="ols",
                     trendline_scope="overall")
    fig.update_layout(paper_bgcolor='rgba(0,0,0,0)',
                      plot_bgcolor='rgba(0,0,0,0)')
    return fig


# app.run_server(mode='inline', port=8070, dev_tools_ui=True, use_reloader = False,
#                dev_tools_hot_reload=False, threaded=    True)

app.run_server(debug=True, use_reloader=False, mode='inline')
vestland
  • 55,229
  • 37
  • 187
  • 305
  • Hi vestland, This is amazing! This project ballooned into more than i expected, but I wanted to say thank you for all the input! this is amazing. I needed to change the web port the app ran on, but got it to work!! – MartyMcFly Feb 13 '23 at 01:32