Skip to content

Commit

Permalink
Merge branch 'main' into add_docker
Browse files Browse the repository at this point in the history
  • Loading branch information
surchs authored Jun 7, 2024
2 parents 1e6e830 + b72fa45 commit ae3f688
Show file tree
Hide file tree
Showing 13 changed files with 855 additions and 244 deletions.
6 changes: 5 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
---
# Documentation
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
Expand All @@ -7,3 +6,8 @@ updates:
directory: /
schedule:
interval: monthly

- package-ecosystem: gitsubmodule
directory: /
schedule:
interval: daily
1 change: 0 additions & 1 deletion .github/workflows/run_precommit.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
---
name: run pre-commit

on:
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/update_precommit_hooks.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
---
name: Update precommit hooks


Expand Down
11 changes: 3 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
---
exclude: |
(?x)^(
data/data_dictionaries/.*.tsv |
Expand All @@ -19,17 +18,13 @@ repos:
- id: mixed-line-ending
- id: trailing-whitespace

- repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
rev: 0.2.3
hooks:
- id: yamlfmt
args: [--mapping, '4', --sequence, '4', --offset, '0']

- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.13.0
hooks:
- id: pretty-format-toml
args: [--autofix, --indent, '4']
- id: pretty-format-yaml
args: [--autofix, --indent, '4']

- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.4.2
Expand All @@ -42,7 +37,7 @@ repos:
- id: isort

- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
rev: v2.3.0
hooks:
- id: codespell

Expand Down
111 changes: 101 additions & 10 deletions climate_emotions_map/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import dash_mantine_components as dmc
from dash import (
ALL,
Dash,
Input,
Output,
Expand All @@ -11,14 +12,19 @@
ctx,
no_update,
)
from dash.exceptions import PreventUpdate

from . import utility as utils
from .data_loader import NATIONAL_SAMPLE_SIZE, SURVEY_DATA
from .layout import construct_layout
from .layout import SINGLE_SUBQUESTION_FIG_KW, construct_layout
from .make_descriptive_plots import make_descriptive_plots
from .make_map import make_map
from .make_stacked_bar_plots import make_stacked_bar
from .utility import DEFAULT_QUESTION # IMPACT_COLORMAP,; OPINION_COLORMAP,
from .utility import ( # IMPACT_COLORMAP,; OPINION_COLORMAP,
ALL_STATES_LABEL,
DEFAULT_QUESTION,
SECTION_TITLES,
)

# Currently needed by DMC, https://www.dash-mantine-components.com/getting-started#simple-usage
_dash_renderer._set_react_version("18.2.0")
Expand All @@ -41,18 +47,31 @@
Output("party-stratify-switch", "disabled"),
],
[
Input("us-map", "clickData"),
Input("party-stratify-switch", "checked"),
Input("state-select", "value"),
],
prevent_initial_call=True,
)
def disable_state_select_and_party_switch_interaction(
is_party_stratify_checked, selected_state
def update_state_and_disable_state_select_and_party_switch_interaction(
figure, is_party_stratify_checked, selected_state
):
"""
Disable the state dropdown when the party stratify switch is checked,
Update the state dropdown when a specific state is clicked (if party stratify switch is not checked),
disable the state dropdown when the party stratify switch is checked,
and disable the party stratify switch when a specific state is selected (i.e., not None).
"""
if ctx.triggered_id == "us-map":
map_selected_state = figure["points"][0]["customdata"][0]

if is_party_stratify_checked or map_selected_state == selected_state:
raise PreventUpdate
return (
map_selected_state,
no_update,
False,
True,
)
if ctx.triggered_id == "party-stratify-switch":
# Deselect any state
return None, is_party_stratify_checked, no_update, no_update
Expand Down Expand Up @@ -80,7 +99,7 @@ def drawer_toggle(n_clicks, opened):
def update_drawer_state(value):
"""Callback function for updating the state in the drawer."""
if value is None:
return "National"
return ALL_STATES_LABEL
return f"State: {value}"


Expand Down Expand Up @@ -173,7 +192,7 @@ def update_map(question_value, state, impact):


@callback(
Output("stacked-bar-plot", "figure"),
Output("selected-question-bar-plot", "figure"),
[
Input("question-select", "value"),
Input("state-select", "value"),
Expand All @@ -182,13 +201,13 @@ def update_map(question_value, state, impact):
],
prevent_initial_call=True,
)
def update_stacked_bar_plots(
def update_selected_question_bar_plot(
question_value,
state,
is_party_stratify_checked,
show_all_responses_checked,
):
"""Update the stacked bar plots based on the selected question."""
"""Update the stacked bar plot for the selected question based on the selected criteria."""
question, subquestion = utils.extract_question_subquestion(question_value)

if show_all_responses_checked:
Expand All @@ -202,10 +221,82 @@ def update_stacked_bar_plots(
state=state,
stratify=is_party_stratify_checked,
threshold=threshold,
binarize_threshold=True,
fig_kw=SINGLE_SUBQUESTION_FIG_KW,
)
return figure


@callback(
Output("selected-question-title", "children"),
Input("state-select", "value"),
prevent_initial_call=True,
)
def update_selected_question_title(state):
"""Update the title for the selected question based on the selected state."""
if state is None:
return f"{SECTION_TITLES['selected_question']}, {ALL_STATES_LABEL}"
return f"{SECTION_TITLES['selected_question']}, {state}"


@callback(
Output("selected-question-container", "display"),
Input("impact-select", "value"),
prevent_initial_call=True,
)
def toggle_selected_question_bar_plot_visibility(impact):
"""Toggle visibility of the selected question bar plot component based on whether an impact is selected."""
if impact is not None:
return "none"
return "flex"


@callback(
Output("all-questions-title", "children"),
Input("state-select", "value"),
)
def update_all_questions_title(state):
"""Update the title for the section for all questions based on the selected state."""
if state is None:
return f"{SECTION_TITLES['all_questions']}, {ALL_STATES_LABEL}"
return f"{SECTION_TITLES['all_questions']}, {state}"


@callback(
Output({"type": "stacked-bar-plot", "question": ALL}, "figure"),
[
Input("state-select", "value"),
Input("party-stratify-switch", "checked"),
Input("response-threshold-control", "checked"),
],
prevent_initial_call=True,
)
def update_stacked_bar_plots(
state,
is_party_stratify_checked,
show_all_responses_checked,
):
"""Update the stacked bar plots for all questions based on the selected criteria."""
figures = []
for output in ctx.outputs_list:
# Example: {'id': {'question': 'q2', 'type': 'stacked-bar-plot'}, 'property': 'figure'}
question = output["id"]["question"]

if show_all_responses_checked:
threshold = None
elif not show_all_responses_checked:
threshold = DEFAULT_QUESTION["outcome"]

figure = make_stacked_bar(
question=question,
subquestion="all",
state=state,
stratify=is_party_stratify_checked,
threshold=threshold,
)
figures.append(figure)

return figures


if __name__ == "__main__":
app.run(debug=True)
36 changes: 36 additions & 0 deletions climate_emotions_map/data_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ def load_data_dictionary(file: str) -> pd.DataFrame:
return pd.read_csv(
Path(__file__).parents[1] / "data" / "data_dictionaries" / file,
sep="\t",
# Some data dictionaries have "None" as a meaningful value, so we have to prevent it
# from being interpreted as a NaN by pandas
keep_default_na=False,
)


Expand Down Expand Up @@ -63,11 +66,13 @@ def load_survey_data() -> dict[str, pd.DataFrame]:
def load_data_dictionaries() -> dict[str, pd.DataFrame]:
"""Load the data dictionaries for the survey data."""
data_files = [
"demographics_dictionary.tsv",
"impacts_list.tsv",
"outcome_dictionary.tsv",
"question_dictionary.tsv",
"state_abbreviations.tsv",
"subquestion_dictionary.tsv",
"threshold_dictionary.tsv",
]

data_frames = {}
Expand All @@ -91,6 +96,34 @@ def load_geojson_objects() -> dict:
return geojson_objects


# TODO: Maybe just save result in a file/fixed dict and load it in the app?
def get_subquestion_order():
"""
Return the order of subquestions for each question based on descending order of % endorsement
for the 3+ outcome, for the whole sample.
"""
df = SURVEY_DATA["opinions_wholesample.tsv"]
# TODO: Fix this hack!
df_thres = df.loc[
(df["outcome"] == "3+") & (df["sub_question"] != "noanswer")
]

subquestion_order = {}
for question, group in df_thres.groupby("question"):
subquestion_order[question] = group.sort_values(
by="percentage", ascending=False
)["sub_question"].tolist()

return subquestion_order


def get_domain_text() -> dict[str, str]:
"""Return a dictionary where key-value pairs are short and full names of each available domain."""
df = DATA_DICTIONARIES["question_dictionary.tsv"]
df_unique = df[["domain_short", "domain_text"]].drop_duplicates()
return dict(zip(df_unique["domain_short"], df_unique["domain_text"]))


SURVEY_DATA = load_survey_data()
# NOTE: column dtypes for opinions data TSVs
# Input:
Expand All @@ -103,6 +136,7 @@ def load_geojson_objects() -> dict:
# outcome object
# percentage float64
# dtype: object
SUBQUESTION_ORDER = get_subquestion_order()

DATA_DICTIONARIES = load_data_dictionaries()
# NOTE: column dtypes for data dictionary TSVs
Expand All @@ -117,5 +151,7 @@ def load_geojson_objects() -> dict:
# ignore bool
# dtype: object

DOMAIN_TEXT = get_domain_text()

NATIONAL_SAMPLE_SIZE = SURVEY_DATA["samplesizes_state.tsv"]["n"].sum()
GEOJSON_OBJECTS = load_geojson_objects()
Loading

0 comments on commit ae3f688

Please sign in to comment.