Reputation: 63
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.
Upvotes: 1
Views: 1031
Reputation: 61084
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:
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')
Upvotes: 1
Reputation: 1679
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)
I'm not sure that dropdown button on graph can change title of axis or not but callback can.
Upvotes: 1