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

Adding Survey Responses to Public Dashboard #124

Merged
merged 75 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
758d2d8
first draft of survey_responses notebook
Mar 13, 2024
a526257
add quality text to pie charts
Mar 15, 2024
c4cfdfa
revise the way surveys are read
Mar 20, 2024
427f44b
update dictionary building
Mar 20, 2024
94f48c5
only display questions currently in the survey
Mar 20, 2024
7f3d477
connect charts to frontend
Mar 22, 2024
74768f7
translate answers, drop zero other
Mar 22, 2024
99e0cc8
updates while working with washingtoncommons
Mar 22, 2024
eb14800
add check to prevent showing "label" questions
Mar 25, 2024
3a3533d
clean up some print statements
Mar 25, 2024
f832c83
drop columns that don't represent questions
Mar 25, 2024
dc3bab8
fix failed charts and alt text
Mar 26, 2024
ea0c660
no need to drop columns
Mar 26, 2024
157ad15
sensed instead of labeled trips by mode
Mar 26, 2024
e6c7fe6
pull out "input" type questions
Mar 29, 2024
8ac86b7
draft "trips presented" changes
Apr 2, 2024
5fe10e1
increase accuracy of quality text
Apr 3, 2024
7d8a114
bypass evals for non-conditional surveys
Apr 3, 2024
8a2c2f4
flatten entire row instead of pulling values
Apr 3, 2024
a04a316
add eval string as a parameter to filter fncn
Apr 3, 2024
af3dfa8
add the survey info parameter
Apr 3, 2024
399c7b3
add new notebook to crontab
Apr 3, 2024
75d4fb6
deal with missing data
Apr 3, 2024
45026b7
clean up notebook
Apr 3, 2024
733e559
remove old code
Apr 3, 2024
8a4869b
no "input" type questions in html
Apr 4, 2024
df6fde3
edits to quality text
Apr 5, 2024
d6b2d80
add sensed metrics to survey dashboards
Apr 9, 2024
0da6c33
comments for every case of dashboard
Apr 9, 2024
8ff0b8b
break survey notebook if irrelevant
Apr 9, 2024
8cdd6de
filter trips - required to keep out test users
Apr 9, 2024
4ea4724
debug df per survey
Apr 9, 2024
eff012b
use emcommon to determine survey prompted
Apr 17, 2024
4d0f7fb
Merge remote-tracking branch 'upstream/main' into dashboard-surveys
Apr 18, 2024
6c7496f
add color mapping to surveys
Apr 18, 2024
1e41c35
fix label translations
Apr 18, 2024
80263ad
add informative print statements
Apr 22, 2024
c98b623
add emcommon as a dependency to the yml file
Apr 22, 2024
10fe708
move if conditions
Apr 22, 2024
1b88265
choose a more distinct color pallete
Apr 22, 2024
1a7b911
clean up inputs cell
Apr 22, 2024
0fae9fc
change condition for the debug dataframe
Apr 23, 2024
f1e94f5
missing punctuation - fix corrupt notebook
Apr 23, 2024
61fa0f1
Merge remote-tracking branch 'AnantasCode/Change-from-Pie-Charts-to-1…
Abby-Wheelis May 4, 2024
3fd5d01
Merge remote-tracking branch 'AnantasCode/Change-from-Pie-Charts-to-1…
Abby-Wheelis May 5, 2024
e46a4b6
Merge remote-tracking branch 'AnantasCode/Change-from-Pie-Charts-to-1…
Abby-Wheelis May 5, 2024
0f31ed6
switch from composite to confirmed trips
Abby-Wheelis May 5, 2024
596886a
update translation of options - issues with numbers
Abby-Wheelis May 5, 2024
8d0e285
update color mapping
Abby-Wheelis May 5, 2024
130b732
Merge remote-tracking branch 'upstream/main' into dashboard-surveys
Abby-Wheelis May 6, 2024
fd16cad
introduce workaround for missing colors
Abby-Wheelis May 6, 2024
52bc509
add todo comments, remove old code
Abby-Wheelis May 6, 2024
548a449
update how the dataframe is formed
Abby-Wheelis May 6, 2024
7e64d12
update dataframe create
Abby-Wheelis May 6, 2024
feb80dc
bump up the emcommon version to latest release
Abby-Wheelis May 7, 2024
2dbd723
reintroduce "assigned" surveys, update quality text
Abby-Wheelis May 7, 2024
b02c1de
update import conventions
Abby-Wheelis May 7, 2024
5e79051
remove outdated code
Abby-Wheelis May 7, 2024
7dbef2d
update import styles
Abby-Wheelis May 7, 2024
39d1156
remove reliance on "total" sets
Abby-Wheelis May 7, 2024
7c7a718
push legend below chart if the first label is long
Abby-Wheelis May 7, 2024
6c38e97
update frontend for metrics
Abby-Wheelis May 7, 2024
a57a30f
remove olde code
Abby-Wheelis May 7, 2024
b207efa
create survey_metrics notebook
Abby-Wheelis May 7, 2024
6957500
add survey metrics to the front end
Abby-Wheelis May 7, 2024
2e2e269
remove testing-only code
Abby-Wheelis May 7, 2024
f9f3ba5
use correct debug frame
Abby-Wheelis May 7, 2024
3d4c5fb
restore default parameters
Abby-Wheelis May 7, 2024
40c1478
add survey metrics to list of stacked
Abby-Wheelis May 7, 2024
6a36770
handle empty dataframes better
Abby-Wheelis May 7, 2024
41db5cf
handle "Other" values in color map
Abby-Wheelis May 7, 2024
fb7bc70
tidy code
Abby-Wheelis May 7, 2024
334c95b
🔥 Remove git and em-common since they are in the server repo already
shankari May 8, 2024
ef4786e
⬆️ Upgrade base image
shankari May 8, 2024
91cc8e7
♻️ Minor fixes to the survey metrics
shankari May 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 63 additions & 6 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@
const end_year = date.getFullYear();
var current_month = start_month;
var current_year = start_year;

dates.push([current_month, current_year]);
while (!(current_month == end_month && current_year == end_year)) {
current_month += 1;
Expand All @@ -356,6 +357,28 @@
};
return dates;
};

function getDictionaryList(form_list) {
var quest_dict = {};
return new Promise(async (resolve) => {
for (i in form_list) {
response = await fetch(form_list[i]);
text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, "text/xml");
labels = doc.getElementsByTagName("label");
for (i in labels) {
try {
if ((labels[i].parentNode.getAttribute("appearance") !== "label" && labels[i].parentNode.nodeName != "input")) //label type questions don't ever have answers
{
quest_dict[labels[i].parentNode.getAttribute("ref").split('/').slice(-1)] = labels[i].firstChild.data;
}
} catch (e) { }
}
}
resolve(quest_dict);
})
};
</script>

<script type="text/javascript">
Expand Down Expand Up @@ -407,7 +430,40 @@
mode_studied = data.intro.mode_studied
// Load list of plots corresponding to study/program
dynamic_labels = data.label_options
if (data.intro.program_or_study == 'program') {
surveys = data.survey_info.surveys
console.log(data.survey_info['trip-labels'])
if (data.survey_info['trip-labels'] === 'ENKETO') { //CASE: SURVEYS
survey_list = Object.keys(surveys)
survey_list = survey_list.filter(name => name !== 'UserProfileSurvey')

sheet_list = []
for (name in survey_list) {
form_path = data.survey_info.surveys[survey_list[name]].formPath;
//THIS ASSUMES THE FILENAME IS THE SAME AS THE FORM PATH BUT WITH xml FILE TYPE
l_path = form_path.split('.')
l_path.splice(l_path.length -1, 1, 'xml');
console.log(l_path);
sheet_path = l_path.join('.')
sheet_list.push(sheet_path)
}

getDictionaryList(sheet_list).then((quest_dict) => {
console.log(quest_dict);
load_file = "metrics_study_surveys.html"
$.get(load_file, function (file) {
Object.entries(quest_dict).forEach(([key, value]) => {
var text = '<option ' + 'value="' + key + '" data-sizex="10" data-sizey="4">' + value + '</option>';
file = file.concat('\n', text);
});
console.log("configuring units");
const unitConfigured = file.replaceAll("${data.display_config.use_imperial}", dist_units);
$('#metric').append(unitConfigured);
addPreconfiguredMetrics(Object.keys(quest_dict).slice(0, 5)); //only adding the first 6 elements
});
});

}
else if (data.intro.program_or_study == 'program') { //CASE: PROGRAM
// Note: We're disabling energy metrics on public dashboard when dynamic labels are available.
// TODO: Remove the if (data.label_options) in future when energy computation is handled properly.
if (dynamic_labels) {
Expand Down Expand Up @@ -515,7 +571,7 @@
}
}).data('gridster');

$('.js-add-new').on('click', function () {
$('.js-add-new').on('click', async function () {
const metric = $("#metric").val();
const dateVal = $("#date").val();
const program = $("#program").val();
Expand All @@ -533,10 +589,11 @@
const htmlFile = "plots/" + metric + "_" + dateVal + program + ".html";
const altTextFile = "plots/" + metric + "_" + dateVal + program + ".txt";
const altText = loadFile(altTextFile);
const isStackedMetric = ['ntrips_total', 'ntrips_purpose', 'ntrips_under80', 'ntrips_commute_mode_confirm',
'total_trip_length', 'total_trip_length_land',`ntrips_${mode_studied}_total`,
`ntrips_${mode_studied}_purpose`,`total_trip_length_${mode_studied}_replaced_mode`]
.includes(metric);
const quest_dict = await getDictionaryList(sheet_list);
const stackedMetrics = ['ntrips_total', 'ntrips_total_survey', 'ntrips_purpose', 'ntrips_under80', 'ntrips_under80_survey', 'ntrips_commute_mode_confirm',
'total_trip_length', 'total_trip_length_land', 'total_trip_length_land_survey',`ntrips_${mode_studied}_total`,
`ntrips_${mode_studied}_purpose`,`total_trip_length_${mode_studied}_replaced_mode`].concat(Object.keys(quest_dict));
const isStackedMetric = stackedMetrics.includes(metric);
const jsonData = { metric, dateVal, program, metricLabel, dateLabel, programLabel, sizex, sizey };

if (isStackedMetric){
Expand Down
10 changes: 10 additions & 0 deletions frontend/metrics_study_surveys.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!-- htmnl options should be 1 per chart question -->
<!-- <option value="question key" data-sizex="4" data-sizey="4">translated question</option> -->

<option value="ntrips_total_survey" data-sizex="10" data-sizey="4">Number of trips</option>
<option value="ntrips_under80_survey" data-sizex="10" data-sizey="4">Number of trips (under 80th percentile of total trips)</option>
<option value="total_trip_length_land_survey" data-sizex="10" data-sizey="4">Total trip length (${data.display_config.use_imperial}) covered by mode in land</option>
<option value="average_miles_sensed_mode" data-sizex="6" data-sizey="4">Average trip length (${data.display_config.use_imperial}) (sensed)</option>
<option value="ntrips_sensed_per_day" data-sizex="6" data-sizey="4">Trip frequency (sensed)</option>
<option value="ntrips_sensed_per_weekday" data-sizex="6" data-sizey="4">Trip frequency (weekday, sensed)</option>
<option value="ts_users" data-sizex="8" data-sizey="2">Timeseries of active users</option>
2 changes: 1 addition & 1 deletion viz_scripts/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# python 3
FROM shankari/e-mission-server:master_2024-04-15--53-23
FROM shankari/e-mission-server:master_2024-05-06--36-33

VOLUME /plots

Expand Down
3 changes: 2 additions & 1 deletion viz_scripts/bin/generate_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ def compute_for_date(month, year):
include_test_users=dynamic_config.get('metrics', {}).get('include_test_users', False),
dynamic_labels = dynamic_labels,
use_imperial = dynamic_config.get('display_config', {}).get('use_imperial', True),
sensed_algo_prefix=dynamic_config.get('metrics', {}).get('sensed_algo_prefix', "cleaned"))
sensed_algo_prefix=dynamic_config.get('metrics', {}).get('sensed_algo_prefix', "cleaned"),
survey_info = dynamic_config.get('survey_info', {}))

print(f"Running at {arrow.get()} with params {params}")

Expand Down
2 changes: 1 addition & 1 deletion viz_scripts/docker/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# python 3
FROM shankari/e-mission-server:master_2024-04-15--53-23
FROM shankari/e-mission-server:master_2024-05-06--36-33

VOLUME /plots

Expand Down
2 changes: 2 additions & 0 deletions viz_scripts/docker/crontab
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
0 8 * * * python bin/generate_plots.py mode_specific_metrics.ipynb default >> /var/log/intake.stdinout 2>&1
0 8 * * * python bin/generate_plots.py mode_specific_timeseries.ipynb default >> /var/log/intake.stdinout 2>&1
0 8 * * * python bin/generate_plots.py energy_calculations.ipynb default >> /var/log/intake.stdinout 2>&1
0 8 * * * python bin/generate_plots.py survey_responses.ipynb default >> /var/log/intake.stdinout 2>&1
0 8 * * * python bin/generate_plots.py survey_metrics.ipynb default >> /var/log/intake.stdinout 2>&1
shankari marked this conversation as resolved.
Show resolved Hide resolved
# For testing only
# */5 * * * * python bin/generate_plots.py mode_purpose_share.ipynb default >> /var/log/intake.stdinout 2>&1
13 changes: 10 additions & 3 deletions viz_scripts/plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ def merge_small_entries(labels, values):
# We could have already had a non-zero other, and it could be small or large
if "Other" not in v2l_df.index:
# zero other will end up with misc_count
v2l_df.loc["Other"] = misc_count
if misc_count.vals > 0:
v2l_df.loc["Other"] = misc_count
elif "Other" in small_chunk.index:
# non-zero small other will already be in misc_count
v2l_df.loc["Other"] = misc_count
else:
# non-zero large other, will not already be in misc_count
v2l_df.loc["Other"] = v2l_df.loc["Other"] + misc_count

disp.display(v2l_df)

return (v2l_df.index.to_list(),v2l_df.vals.to_list(), v2l_df.pct.to_list())
Expand Down Expand Up @@ -128,8 +130,13 @@ def plot_and_text_stacked_bar_chart(df, bar_label, ax, text_result, colors, debu
ax.tick_params(axis='y', labelsize=18)
ax.tick_params(axis='x', labelsize=18, rotation=90)
ncols = len(df_only_small)//5 if len(df_only_small) % 5 == 0 else len(df_only_small)//5 + 1
ax.legend(bbox_to_anchor=(1, 0), loc='lower left', fancybox=True, shadow=True, fontsize=15)
# ax.legend(bbox_to_anchor=(1, 1), loc='upper left', fancybox=True, shadow=True, fontsize=15, ncols=ncols)

if len(pd.unique(df_only_small['Label'])[0]) > 15:
ax.legend(bbox_to_anchor=(0.5, -0.5), loc='upper center', fancybox=True, shadow=True, fontsize=15)
else:
ax.legend(bbox_to_anchor=(1, 0), loc='lower left', fancybox=True, shadow=True, fontsize=15)
# ax.legend(bbox_to_anchor=(1, 1), loc='upper left', fancybox=True, shadow=True, fontsize=15, ncols=ncols)

# Fix for the error: RuntimeError("Unknown return type"), adding the below line to address as mentioned here https://github.com/matplotlib/matplotlib/issues/25625/
ax.set_xlim(right=ax.get_xlim()[1] + 1.0, auto=True)
text_result[0], text_result[1] = store_alt_text_and_html_stacked_bar_chart(df_all_entries, bar_label)
Expand Down
24 changes: 24 additions & 0 deletions viz_scripts/scaffolding.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ def load_all_participant_trips(program, tq, load_test_users):
disp.display(participant_ct_df.head())
return participant_ct_df

def filter_composite_trips(all_comp_trips, program, load_test_users):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that you have removed load_composite_trips, you can also remove filter_composite_trips

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that filter_composite_trips is not identical to filter_labeled_trips because it doesn't filter by blank user input. However, I don't understand why you are not filtering by blank user input. It should be possible to just call load_all_participant_trips instead of load_all_confirmed_trips followed by filter_composite_trips to achieve the same result.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, I don't understand why you are not filtering by blank user input.

I do, later, but I need all of the trips in order to create the "all trips for which a survey was prompted" information

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made some changes to the data loading (including pushing it to scaffolding) in #135 - I have the filtered, unfiltered, and file suffix all returned from a function in scaffolding now - I don't have the debug_df or quality_text included since it varies chart to chart

participant_list = get_participant_uuids(program, load_test_users)
# CASE 1 of https://github.com/e-mission/em-public-dashboard/issues/69#issuecomment-1256835867
if len(all_comp_trips) == 0:
return all_comp_trips
participant_ct_df = all_comp_trips[all_comp_trips.user_id.isin(participant_list)]
print("After filtering, found %s participant trips " % len(participant_ct_df))
disp.display(participant_ct_df.head())
return participant_ct_df

def filter_labeled_trips(mixed_trip_df):
# CASE 1 of https://github.com/e-mission/em-public-dashboard/issues/69#issuecomment-1256835867
if len(mixed_trip_df) == 0:
Expand Down Expand Up @@ -214,6 +224,20 @@ def mapping_color_labels(dynamic_labels, dic_re, dic_pur):

return colors_mode, colors_purpose, colors_sensed

# Function: Maps survey answers to colors.
# Input: dictionary of raw and translated survey answers
# Output: Map for color with survey answers
def mapping_color_surveys(dic_options):
dictionary_values = (list(OrderedDict.fromkeys(dic_options.values())))

colors = {}
for i in range(len(dictionary_values)):
colors[dictionary_values[i]] = plt.cm.tab10.colors[i%10]

colors['Other'] = plt.cm.tab10.colors[(i+1)%10]

return colors

def load_viz_notebook_sensor_inference_data(year, month, program, include_test_users=False, sensed_algo_prefix="cleaned"):
""" Inputs:
year/month/program = parameters from the visualization notebook
Expand Down
Loading