-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Convert streamlit dashboard to Dash #938
Changes from 10 commits
ea7d580
fa58f99
409199c
40a6a2b
75d9279
e38aee4
901bace
e0a943c
09c02ba
7e8b3f6
4c10522
81c0494
9fa719a
8708db6
2887ca6
6d6a174
b615faa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,11 @@ | ||
from typing import List | ||
from typing import List, Optional | ||
|
||
from enforce_typing import enforce_types | ||
import numpy as np | ||
|
||
from pdr_backend.aimodel.aimodel import Aimodel | ||
|
||
|
||
@enforce_types | ||
class AimodelPlotdata: | ||
"""Simple class to manage many inputs going into plot_model.""" | ||
|
||
|
@@ -17,14 +16,18 @@ def __init__( | |
ytrue_train: np.ndarray, | ||
colnames: List[str], | ||
slicing_x: np.ndarray, | ||
sweep_vars: Optional[List[int]] = None, | ||
): | ||
""" | ||
@arguments | ||
model -- Aimodel | ||
X_train -- 2d array [sample_i, var_i]:cont_value -- model trn inputs | ||
ytrue_train -- 1d array [sample_i]:bool_value -- model trn outputs | ||
colnames -- [var_i]:str -- name for each of the X inputs | ||
slicing_x -- arrat [dim_i]:floatval - when >2 dims, plot about this pt | ||
slicing_x -- array [var_i]:floatval - values for non-sweep vars | ||
sweep_vars -- list with [sweepvar_i] or [sweepvar_i, sweepvar_j] | ||
-- If 1 entry, do line plot (1 var), where y-axis is response | ||
-- If 2 entries, do contour plot (2 vars), where z-axis is response | ||
""" | ||
# preconditions | ||
assert X_train.shape[1] == len(colnames) == slicing_x.shape[0], ( | ||
|
@@ -36,15 +39,30 @@ def __init__( | |
X_train.shape[0], | ||
ytrue_train.shape[0], | ||
) | ||
assert sweep_vars is None or len(sweep_vars) in [1, 2] | ||
|
||
# set values | ||
self.model = model | ||
self.X_train = X_train | ||
self.ytrue_train = ytrue_train | ||
self.colnames = colnames | ||
self.slicing_x = slicing_x | ||
self.sweep_vars = sweep_vars | ||
|
||
@property | ||
@enforce_types | ||
def n(self) -> int: | ||
"""Number of input dimensions == # columns in X""" | ||
return self.X_train.shape[1] | ||
|
||
@property | ||
@enforce_types | ||
def n_sweep(self) -> int: | ||
"""Number of variables to sweep in the plot""" | ||
if self.sweep_vars is None: | ||
return 0 | ||
|
||
Comment on lines
+62
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if self.sweep_vars is None:
return 0 Delete this ^, in line with comment above. (Sweep vars should be a list with one or two entries) |
||
if self.n == 1: | ||
return 1 | ||
|
||
return len(self.sweep_vars) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,25 @@ | ||
from enforce_typing import enforce_types | ||
import numpy as np | ||
import plotly.graph_objects as go | ||
from enforce_typing import enforce_types | ||
|
||
from pdr_backend.aimodel.aimodel_plotdata import AimodelPlotdata | ||
|
||
|
||
@enforce_types | ||
def plot_aimodel_response( | ||
aimodel_plotdata: AimodelPlotdata, | ||
): | ||
def plot_aimodel_response(aimodel_plotdata: AimodelPlotdata): | ||
""" | ||
@description | ||
Plot the model response in a line plot (1 var) contour plot (>1 vars) | ||
Plot the model response in a line plot (1 var) or contour plot (2 vars). | ||
And overlay X-data. (Training data or otherwise.) | ||
If the model has >2 vars, it plots the 2 most important vars. | ||
|
||
@arguments | ||
aimodel_plotdata -- holds: | ||
model -- Aimodel | ||
X_train -- array [sample_i][var_i]:floatval -- trn model inputs (or other) | ||
ytrue_train -- array [sample_i]:boolval -- trn model outputs (or other) | ||
colnames -- list [var_i]:X_column_name | ||
slicing_x -- arrat [var_i]:floatval - when >2 dims, plot about this pt | ||
fig_ax -- None or (fig, ax) to easily embed into existing plot | ||
legend_loc -- eg "upper left". Applies only to contour plots. | ||
""" | ||
if aimodel_plotdata.n == 1: | ||
return _plot_aimodel_lineplot(aimodel_plotdata) | ||
d = aimodel_plotdata | ||
# assert d.n_sweep in [1, 2] | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This should not be commented out. (In line with comments above) |
||
if d.n_sweep == 1 and d.n == 1 or d.n == 1: | ||
return _plot_aimodel_lineplot_1var(aimodel_plotdata) | ||
|
||
if d.n_sweep == 1 and d.n > 1: | ||
return _plot_aimodel_lineplot_nvars(aimodel_plotdata) | ||
|
||
return _plot_aimodel_contour(aimodel_plotdata) | ||
|
||
|
@@ -35,15 +28,17 @@ def plot_aimodel_response( | |
|
||
|
||
@enforce_types | ||
def _plot_aimodel_lineplot(aimodel_plotdata: AimodelPlotdata): | ||
def _plot_aimodel_lineplot_1var(aimodel_plotdata: AimodelPlotdata): | ||
""" | ||
@description | ||
Plot the model, when there's 1 input x-var. Use a line plot. | ||
Do a 1d lineplot, when exactly 1 input x-var | ||
Will fail if not 1 var. | ||
Because one var total, we can show more info of true-vs-actual | ||
""" | ||
# aimodel data | ||
assert aimodel_plotdata.n == 1 | ||
d = aimodel_plotdata | ||
assert d.n == 1 | ||
assert d.n_sweep == 1 | ||
X, ytrue = d.X_train, d.ytrue_train | ||
|
||
x = X[:, 0] | ||
|
@@ -128,6 +123,49 @@ def _plot_aimodel_lineplot(aimodel_plotdata: AimodelPlotdata): | |
return fig_bars | ||
|
||
|
||
@enforce_types | ||
def _plot_aimodel_lineplot_nvars(aimodel_plotdata: AimodelPlotdata): | ||
""" | ||
@description | ||
Do a 1d lineplot, when >1 input x-var, and we have chosen the var. | ||
Because >1 var total, we can show more info of true-vs-actual | ||
""" | ||
# input data | ||
d = aimodel_plotdata | ||
assert d.n >= 1 | ||
assert d.n_sweep == 1 | ||
|
||
# construct sweep_x | ||
sweepvar_i = d.sweep_vars[0] # type: ignore[index] | ||
mn_x, mx_x = min(d.X_train[:, sweepvar_i]), max(d.X_train[:, sweepvar_i]) | ||
N = 200 | ||
sweep_x = np.linspace(mn_x, mx_x, N) | ||
|
||
# construct X | ||
X = np.empty((N, d.n), dtype=float) | ||
X[:, sweepvar_i] = sweep_x | ||
for var_i in range(d.n): | ||
if var_i == sweepvar_i: | ||
continue | ||
X[:, var_i] = d.slicing_x[var_i] | ||
|
||
# calc model response | ||
yptrue = d.model.predict_ptrue(X) # [sample_i]: prob_of_being_true | ||
|
||
# line plot: model response surface | ||
fig_line = go.Figure( | ||
data=go.Scatter( | ||
x=sweep_x, | ||
y=yptrue, | ||
mode="lines", | ||
line={"color": "gray"}, | ||
name="model prob(true)", | ||
) | ||
) | ||
|
||
return fig_line | ||
|
||
|
||
@enforce_types | ||
def _plot_aimodel_contour( | ||
aimodel_plotdata: AimodelPlotdata, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't make this optional, make it mandatory. If it's optional, it's too much risk of someone forgetting to specify it when they mean to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that's feasible. We create the aimodel itself, then we sweep through it. We don't even know what we are going to sweep when we create it. I added sweep_vars as None when we don't sweep anything. We can adjust this as needed, but in case the property doesn't exist I consider all vars.