Skip to content
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

[ENH] Allow stratifying phenotypic column histogram by session #106

Merged
merged 8 commits into from
Nov 8, 2023
33 changes: 28 additions & 5 deletions proc_dash/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,16 +298,17 @@ def update_matching_rows(columns, virtual_data):
Output("interactive-datatable", "filter_query"),
Output("session-dropdown", "value"),
Output("phenotypic-column-plotting-dropdown", "value"),
Output("session-toggle-switch", "value"),
],
Input("memory-filename", "data"),
prevent_initial_call=True,
)
def reset_selections(filename):
"""
If file contents change (i.e., selected new CSV for upload), reset displayed file name and dropdown filter
selection values. Reset will occur regardless of whether there is an issue processing the selected file.
If file contents change (i.e., selected new CSV for upload), reset displayed file name and selection values related to data filtering or plotting.
Reset will occur regardless of whether there is an issue processing the selected file.
"""
return f"Input file: {filename}", "", "", None
return f"Input file: {filename}", "", "", None, False


@app.callback(
Expand Down Expand Up @@ -413,12 +414,16 @@ def display_phenotypic_column_dropdown(parsed_data):
[
Input("phenotypic-column-plotting-dropdown", "value"),
Input("interactive-datatable", "derived_virtual_data"),
Input("session-toggle-switch", "value"),
],
State("memory-overview", "data"),
prevent_initial_call=True,
)
def plot_phenotypic_column(
selected_column: str, virtual_data: list, parsed_data: dict
selected_column: str,
virtual_data: list,
session_switch_value: bool,
parsed_data: dict,
):
"""When a column is selected from the dropdown, generate a histogram of the column values."""
if selected_column is None or parsed_data.get("type") != "phenotypic":
Expand All @@ -433,8 +438,13 @@ def plot_phenotypic_column(
else:
data_to_plot = virtual_data

if session_switch_value:
color = "session"
else:
color = None

return plot.plot_phenotypic_column_histogram(
pd.DataFrame.from_dict(data_to_plot), selected_column
pd.DataFrame.from_dict(data_to_plot), selected_column, color
), {"display": "block"}


Expand Down Expand Up @@ -474,5 +484,18 @@ def generate_column_summary(
)


@app.callback(
Output("session-toggle-switch", "style"),
Input("phenotypic-column-plotting-dropdown", "value"),
prevent_initial_call=True,
)
def display_session_switch(selected_column: str):
"""When a column is selected from the dropdown, display switch to enable/disable stratifying the plot by session."""
if selected_column is None:
return {"display": "none"}

return {"display": "block"}


if __name__ == "__main__":
app.run_server(debug=True)
20 changes: 19 additions & 1 deletion proc_dash/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,16 @@ def column_summary_card():
)


def session_toggle_switch():
"""Generates a switch that toggles whether the column plot is stratified by session."""
return dbc.Switch(
id="session-toggle-switch",
label="Stratify plot by session",
value=False,
style={"display": "none"},
)


def construct_layout():
"""Generates the overall dashboard layout."""
return html.Div(
Expand Down Expand Up @@ -447,7 +457,15 @@ def construct_layout():
),
width=8,
),
dbc.Col(column_summary_card()),
dbc.Col(
dbc.Stack(
[
column_summary_card(),
session_toggle_switch(),
],
gap=3,
),
),
],
align="center",
),
Expand Down
41 changes: 36 additions & 5 deletions proc_dash/plotting.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from itertools import product
from textwrap import wrap

import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
Expand All @@ -13,7 +14,7 @@
"FAIL": CMAP[9],
"UNAVAILABLE": CMAP[10],
}
HISTO_COLOR = CMAP[0]
CMAP_PHENO = px.colors.qualitative.Vivid

# TODO: could use util.PIPE_COMPLETE_STATUS_SHORT_DESC to define below variable instead
PIPELINE_STATUS_ORDER = ["SUCCESS", "FAIL", "UNAVAILABLE"]
Expand Down Expand Up @@ -133,15 +134,37 @@ def populate_empty_records_pipeline_status_plot(


def plot_phenotypic_column_histogram(
data: pd.DataFrame, column: str
data: pd.DataFrame, column: str, color: str = None
) -> go.Figure:
"""Returns a histogram of the values of the given column across records in the active datatable."""
"""
Returns a histogram of the values of the given column across records in the active datatable.
If the column data are continuous, a box plot of the distribution is also plotted as a subplot.
"""
axis_title_gap = 8 # reduce gap between axis title and axis tick labels
title_fsize = 18
if np.issubdtype(data[column].dtype, np.number):
# NOTE: The default box plot on-hover labels mean/q1/q3 etc. are a bit verbose, but there's no way to customize how they are displayed yet
# (See https://github.com/plotly/plotly.js/pull/3685)
alyssadai marked this conversation as resolved.
Show resolved Hide resolved
marginal = "box"
else:
marginal = None

fig = px.histogram(
wrap_df_column_values(df=data, column=column, width=30),
x=column,
color_discrete_sequence=[HISTO_COLOR],
text_auto=True,
color=color,
color_discrete_sequence=CMAP_PHENO,
marginal=marginal,
)
fig.update_traces(
alyssadai marked this conversation as resolved.
Show resolved Hide resolved
boxmean=True,
notched=False,
jitter=1,
customdata=data["participant_id"],
meta=column,
# customize hover info for data points to include participant_id as well as the column value (x)
hovertemplate="participant_id: %{customdata}<br>%{meta}=%{x}",
selector={"type": "box"},
)
fig.update_layout(
margin=LAYOUTS["margin"],
Expand All @@ -151,5 +174,13 @@ def plot_phenotypic_column_histogram(
**LAYOUTS["title"],
},
bargap=0.1,
barmode="group",
boxgap=0.1,
# Reduce gap between legend and plot area
# (https://plotly.com/python-api-reference/generated/plotly.graph_objects.Layout.html#plotly.graph_objects.layout.Legend.x)
legend={"x": 1.01},
)
fig.update_xaxes(title_standoff=axis_title_gap)
fig.update_yaxes(title_standoff=axis_title_gap)

return fig