Estudio24516/Narrative_Visualization
0
1# Import necessary packages2import pandas as pd3import numpy as np4import altair as alt5import datetime as dt6import panel as pn7import vega_datasets8from vega_datasets import data9import requests10import pycountry11 12# Enable panel extensions13pn.extension(design='bootstrap')14pn.extension('vega')15 16# Create a basic template17template = pn.template.BootstrapTemplate(18 title='COVID-19: Policy Responses and Google Mobility Trends',19)20 21# Set up the main column to hold key content22maincol = pn.Column()23 24# Headline25maincol.append("# How has the COVID-19 pandemic changed the movement of people around the world?")26 27# Load the datasets and data processing28## define a function to get the numeric code from the alpha3 code in the original dataset29def alpha3_to_numeric(alpha3):30 try:31 # return the numeric code for the country32 return str(int(pycountry.countries.get(alpha_3=alpha3).numeric))33 except:34 # returning None if the country code is not found35 return None36## load data for Google Mobility Trends37data = pd.read_excel('Mobility Data.xlsx')38data['ID'] = data['Code'].apply(alpha3_to_numeric)39## convert the 'Day' column to datetime40data['Day'] = pd.to_datetime(data['Day'])41## reshape the data42worldwide_data = data.melt(id_vars=['Entity', 'Code', 'Day', 'ID'], 43 value_vars=['Retail & Recreation', 'Grocery & Pharmacy', 'Residential', 44 'Transit Stations', 'Parks', 'Workplaces'],45 var_name='Category', 46 value_name='Percentage')47## load data for Publict Transportation Policy48public_transportation_data = pd.read_excel('Close Public Transport Data.xlsx')49public_transportation_data['ID'] = public_transportation_data['Code'].apply(alpha3_to_numeric)50## convert the 'Day' column to datetime51public_transportation_data['Day'] = pd.to_datetime(public_transportation_data['Day'])52## load data for Stay Home Requirements Policy53stay_home_data = pd.read_excel('Stay Home Requirements Data.xlsx')54stay_home_data['ID'] = stay_home_data['Code'].apply(alpha3_to_numeric)55## convert the 'Day' column to datetime56stay_home_data['Day'] = pd.to_datetime(stay_home_data['Day'])57## load data for Work Place Closure Policy58work_place_data = pd.read_excel('Workplace Closure Data.xlsx')59work_place_data['ID'] = work_place_data['Code'].apply(alpha3_to_numeric)60## convert the 'Day' column to datetime61work_place_data['Day'] = pd.to_datetime(work_place_data['Day'])62## load data for Gathering Restrictions Policy63gathering_data = pd.read_excel('Restriction Gatherings Data.xlsx')64gathering_data['ID'] = gathering_data['Code'].apply(alpha3_to_numeric)65## convert the 'Day' column to datetime66gathering_data['Day'] = pd.to_datetime(gathering_data['Day'])67alt.data_transformers.disable_max_rows()68 69# Introduction70maincol.append("The coronavirus which causes the [COVID-19 pandemic](https://www.cdc.gov/coronavirus/2019-nCoV/index.html) not only resulted in a global health crisis, but also greatly affected global economic and social environment. In response to the pandemic, nations worldwide have adopted various strict measures to slow the spread of the virus, including **imposing restrictions on public transportation**, **enforcing ‘stay-at-home’ lockdowns**, and **halting social gatherings**. These interventions aim to minimize close contact among individuals, thereby reducing the transmission.")71maincol.append("These measures undeniably have a profound impact on daily life. This raises important questions: **How have these measures affected human mobility?** **What impact have these policy restrictions had on the everyday lives of people worldwide?** Moreover, **why these restrictions are helpful in terms of disease control?**")72maincol.append("To explore the first two questions, we can refer to the [COVID-19 Community Mobility Reports]( https://www.google.com/covid19/mobility/) provided by Google. These reports utilize anonymized location data from applications like Google Maps to generate a consistently updated overview of movement trends during the pandemic. Specifically, it tracks daily visits to places like grocery stores, parks, and transit stations, comparing these figures to a pre-pandemic baseline established from the median activity between *January 3, 2020 and February 6, 2020*. In addition, comparisons are made relative to the baseline with typical daily fluctuations taken into account, which eliminates the influence of people’s different routines on weekdays and weekends.")73maincol.append("The reports are originally presented in PDF form for each country/region, which summarizes key insights in a neat and straightforward form for readers’ reference. However, it sacrifices offering readers an opportunity to explore the data based on their own interests. Therefore, I have transformed this data into interactive visualizations below, which simplify tracking trends of mobility change over time and demonstrating the possible impact of specific restriction policies on human mobility. It is to be noted that **Google advises against making cross-regional comparisons** due to potential discrepancies in location categories that could skew interpretations. Meanwhile, it's important to note that the changes are compared to the early 2020 baseline, disregarding seasonal variations which might, for example, increase the frequency of outdoor activities during warmer months. Thus, some of the shifts may reflect more about seasonal patterns rather than changes induced by COVID-19 policies.")74maincol.append("Throughout this article, some countries/regions or specific mobility categories are picked as typical examples to discuss some interesting insights. However, please feel free to explore based on your own interests by utilizing the selector in interactive visualizations which supports the switch interaction.")75maincol.append(pn.layout.Divider())76 77# Visualization 178maincol.append("## Change in mobility by category")79maincol.append("This interactive chart shows how human mobility in categorized places has changed compared to the baseline. The *Residential* category shows a change in **duration of time** spent at home, while the other categories measure a change in **total number of visits**.")80maincol.append("""81Some tips on how to use this interactive visualization:821. Click on the dropdown **Country/Region** to switch to another country/region.832. Click on the dropdown **Category** to highlight a category you would like to focus on.843. Change the date range by clicking and moving the **date bar** to take a closer look at a specific period.854. Hover over on the line to see **tooltips** including accurate date and value for percentage change from baseline.86""")87## define a function to create the plot88def create_plot(subgroup='United States', date_range=None, selected_category='All'):89 ### filter dataset based on the selected subgroup90 df_filtered = worldwide_data[worldwide_data['Entity'] == subgroup]91 ### apply date range filter if specified92 if date_range is not None:93 start_date, end_date = date_range94 start_date, end_date = pd.to_datetime(start_date), pd.to_datetime(end_date)95 df_filtered = df_filtered[(df_filtered['Day'] >= start_date) & (df_filtered['Day'] <= end_date)] 96 ### create opacity condition related to selection97 opacity_condition = alt.condition(98 alt.datum.Category == selected_category,99 alt.value(1),100 alt.value(0.2) if selected_category != 'All' else alt.value(1)101 )102 ### create line chart103 line_chart = alt.Chart(df_filtered).mark_line(size=2).encode(104 x=alt.X('Day:T',axis=alt.Axis(grid=False)),105 y=alt.Y('Percentage:Q',scale=alt.Scale(domain=[-200, 200]),axis=alt.Axis(values=[-200, -150, -100, -50, 0, 50, 100, 150, 200],grid=True,gridColor='lightgrey',title='Change from baseline (%)')),106 color='Category:N',107 opacity=opacity_condition,108 tooltip=[109 alt.Tooltip('Entity:N', title='Country'),110 alt.Tooltip('Category:N', title='Category'),111 alt.Tooltip('Day:T', title='Date'),112 alt.Tooltip('Percentage:Q', title='Change from baseline(%)', format=".2f")113 ]114 ).properties(115 width=900,116 height=500117 )118 ### return the line chart119 return line_chart120## create widgets for interaction121subgroup_select = pn.widgets.Select(name='Country/Region', options=worldwide_data['Entity'].unique().tolist(), value='United States')122date_range_slider = pn.widgets.DateRangeSlider(name='Date Range', start=worldwide_data['Day'].min(), end=worldwide_data['Day'].max())123category_select = pn.widgets.Select(name='Category', options=['All'] + worldwide_data['Category'].unique().tolist(), value='All')124## define a function to update the plot based on widgets125def update_plot(event):126 plot = create_plot(subgroup=subgroup_select.value, 127 date_range=date_range_slider.value,128 selected_category=category_select.value)129 plot_pane.object = plot130## bind the widgets to the create_plot function131subgroup_select.param.watch(update_plot, 'value')132date_range_slider.param.watch(update_plot, 'value')133category_select.param.watch(update_plot, 'value')134## initial plot135initial_plot = create_plot()136plot_pane = pn.pane.Vega(initial_plot, width=1000, height=500)137maincol.append(pn.Row(subgroup_select,category_select))138maincol.append(date_range_slider)139maincol.append(plot_pane)140maincol.append(" ")141maincol.append(" ")142maincol.append(" ")143maincol.append(" ")144maincol.append(" ")145maincol.append("""146From the visualization, it is easy for us to perceive a general decrease of visits to places such as workplaces, transit stations as well as retail and recreation places at the beginning of the global pandemic. Meanwhile, we also notice an increase of time spent at home. Take **United States** as an example, we see a sharp decrease in number of visits to all categorized places except residential at around March 15, 2020 to March 29, 2020. This period corresponds to the onset of widespread COVID-19 lockdown measures across various states, which were implemented to curb the spread of the virus:147* *March 15, 2020*: The Centers for Disease Control and Prevention (CDC) recommended no gatherings of 50 people or more for the next eight weeks.148* *March 16, 2020*: Many states including Ohio, Maryland, Washington, and California started closing bars, restaurants, and public gatherings to slow the spread of the virus.149* *March 19, 2020*: California issued a statewide stay-at-home order, the first of its kind in the nation.150* *March 21, 2020*: New York State followed with its own stay-at-home order, mandating non-essential workers to stay home.151* *March 23, 2020*: Washington State and Massachusetts issued stay-at-home orders.152* *March 24 to 29, 2020*: Several other states, including Michigan, Indiana, Wisconsin, and West Virginia, issued formal stay-at-home orders.153""")154maincol.append("In addition, throughout the time period, visits to workplaces and transit stations in the U.S. are consistently below the baseline level, suggesting a substantial change in people's patterns of working and commuting. Moreover, the visits to parks in the U.S. show a strong seasonal pattern, indicating that changes might not be induced by COVID-19 policies. However, situations in different countries can be extremely different. For example, in **Mexico**, after a sharp decrease at around *March 15, 2020* to *April 15, 2020*, we see visits to all categories show a trend of increase and end up with all beyond baseline level in *October, 2020*.")155maincol.append(pn.layout.Divider())156maincol.append("## Policy Restrictions and Change in mobility")157maincol.append("Next, we are going to see possible influence of different policy restrictions on human mobility respectively. Data for specific policy restrictions are retrieved from [Oxford COVID-19 Government Response Tracker](https://www.bsg.ox.ac.uk/research/research-projects/oxford-covid-19-government-response-tracker). If policies vary at the subnational level, the index is shown as **the response level of the strictest sub-region**.")158maincol.append("""159Some tips on how to use the following interactive dashboards:1601. Upper left is a line chart showing **the selected country/region’s policy requirements level across time**. Lower left is a line chart showing **the selected country/region’s mobility change from baseline in a specified category across time**. The reference lines in line charts denote **current selected date**.1612. Upper right is a choropleth map showing **the policy requirements level around the globe on the selected date**. Lower right is a choropleth map for **mobility change from baseline in a specified category around the globe on the selected date**. The selected country/region in these maps are highlighted with <span style='color: red;'>**red boundary**</span>.1623. Click on the dropdown **Country/Region** to switch to another country/region.1634. Click on the dropdown **Category** to switch to another category.1645. Move the **date button** along the date bar to select a specific date.1656. **Zoom in and out** based on x-axis with mouse for line charts to delve into details of a period and get back.1667. Hover over on the line or a country/region in the map to see **tooltips** including detailed data.167""")168# Visualization 2169maincol.append("### Policy on public transport")170maincol.append("This interactive dashboard allows us to explore government policies on public transport closures and its influence on human mobility. For the public transportation closure level, the larger the number, the stricter the restrictions are. Particularly, **0** represents <span style='color: #8ca08c;'>**no measures**</span>, **1** represents <span style='color: #fceb8c;'>**recommended closing or reduce volume**</span>, and *2* represents <span style='color: #f4c7c3;'>**required closing or prohibit most using it**</span>. ")171## load TopoJSON for world countries172world_json_url = "https://vega.github.io/vega-datasets/data/world-110m.json"173## create widgets for interaction174category_select_transportation = pn.widgets.Select(name='Category', options=worldwide_data['Category'].unique().tolist(), value=worldwide_data['Category'].unique()[0])175date_slider_transportation = pn.widgets.DateSlider(name='Day', start=worldwide_data['Day'].min(), end=worldwide_data['Day'].max(), value=worldwide_data['Day'].min())176subgroup_select_transportation = pn.widgets.Select(name='Country/Region', options=worldwide_data['Entity'].unique().tolist(), value='United States')177## define the function to create dashboard178def create_transportation_dashboard(date, category, subgroup):179 ### date to datetime180 date = pd.to_datetime(date)181 ### zoom condition for line charts182 zoom = alt.selection_interval(bind='scales', encodings=['x'], name="zoom")183 ### color dictionary184 category_colors = {185 'Retail & Recreation': '#17becf',186 'Grocery & Pharmacy': '#1f77b4',187 'Parks': '#ff7f0e',188 'Transit Stations': '#2ca02c',189 'Workplaces': '#bcbd22',190 'Residential': '#d62728'191 }192 choro_colors = {193 'Retail & Recreation': 'blueorange',194 'Grocery & Pharmacy': 'brownbluegreen',195 'Parks': 'purplegreen',196 'Transit Stations': 'pinkyellowgreen',197 'Workplaces': 'purpleorange',198 'Residential': 'redblue'199 }200 current_category_color = category_colors.get(category, 'black')201 current_choro_color = choro_colors.get(category, 'redyellowgreen')202 ###--------------------category choro part--------------------203 ### filter the data204 filtered_data_category_choro = worldwide_data[(worldwide_data['Day'] == date) & (worldwide_data['Category'] == category)]205 ### create the base for category choro206 base_category = alt.Chart(alt.topo_feature(world_json_url, 'countries')).mark_geoshape(207 stroke='black', strokeWidth=0.5208 ).properties(209 width=600,210 height=300211 ).project('naturalEarth1')212 ### create the category choro213 data_layer = base_category.transform_lookup(214 lookup='id',215 from_=alt.LookupData(filtered_data_category_choro, 'ID', ['Entity', 'Category', 'Percentage']),216 ).encode(217 color=alt.Color('Percentage:Q', 218 scale=alt.Scale(domain=[-100, 100], scheme=current_choro_color), # Using the stepped_colors for the scale219 legend=alt.Legend(title='Change from baseline', orient='right')220 ),221 tooltip=[222 alt.Tooltip('Entity:N', title='Country'),223 alt.Tooltip('Category:N', title='Category'),224 alt.Tooltip('Day:T', title='Date'),225 alt.Tooltip('Percentage:Q', title='Change from baseline(%)', format=".2f")226 ],227 stroke=alt.condition(alt.datum.Entity == subgroup, alt.value('red'),alt.value('black'))228 )229 ### create another background layer230 background = alt.Chart(alt.topo_feature(world_json_url, 'countries')).mark_geoshape(231 fill='lightgray', stroke='black', strokeWidth=0.5232 ).properties(233 width=600,234 height=300235 ).project(236 'naturalEarth1'237 )238 ### combine the choro and the background layer239 category_choro = alt.layer(background, data_layer).resolve_scale(color='independent')240 ###--------------------transportation choro part--------------------241 ### filter the data242 filtered_data_transportation_choro = public_transportation_data[(public_transportation_data['Day'] == date)]243 ### create the base for transportation choro244 base_transportation = alt.Chart(alt.topo_feature(world_json_url, 'countries')).mark_geoshape(245 stroke='black', strokeWidth=0.5246 ).properties(247 width=600,248 height=300249 ).project('naturalEarth1')250 ### create the transportation choro251 transportation_choro = base_transportation.transform_lookup(252 lookup='id',253 from_=alt.LookupData(filtered_data_transportation_choro, 'ID', ['Entity','Close Public Transport']),254 default='No Data'255 ).encode(256 color=alt.Color('Close Public Transport:N', scale=alt.Scale(domain=[0, 1, 2, 'No Data'],257 range=["#8ca08c", "#fceb8c", "#f4c7c3", "#d3d3d3"]),258 legend=alt.Legend(title='Public transport closure level', orient='right', offset=20)),259 tooltip=[alt.Tooltip('Entity:N', title='Country'), alt.Tooltip('Day:T', title='Date'), alt.Tooltip('Close Public Transport:N', title='Closure Level')],260 stroke=alt.condition(alt.datum.Entity == subgroup, alt.value('red'),alt.value('black'))261 )262 ###--------------------category line part--------------------263 filtered_data_category_line = worldwide_data[(worldwide_data['Entity'] == subgroup) & (worldwide_data['Category'] == category)]264 line_chart_category = alt.Chart(filtered_data_category_line).mark_area(color=current_category_color).encode(265 x=alt.X('Day:T',axis=alt.Axis(grid=False)),266 y=alt.Y('Percentage:Q',scale=alt.Scale(domain=[-200, 200]),axis=alt.Axis(values=[-200, -150, -100, -50, 0, 50, 100, 150, 200],grid=True,gridColor='lightgrey',title='Change from baseline (%)')),267 tooltip=[268 alt.Tooltip('Entity:N', title='Country'),269 alt.Tooltip('Category:N', title='Category'),270 alt.Tooltip('Day:T', title='Date'),271 alt.Tooltip('Percentage:Q', title='Change from baseline(%)', format=".2f")272 ]273 ).properties(274 width=400,275 height=300276 ).add_params(277 zoom278 )279 line_chart_category += alt.Chart(pd.DataFrame({'Day': [date]})).mark_rule(color='#e377c2').encode(280 x='Day:T'281 )282 ###--------------------transportation line part--------------------283 filtered_data_transportation_line = public_transportation_data[(public_transportation_data['Entity'] == subgroup)]284 line_chart_transportation = alt.Chart(filtered_data_transportation_line).mark_line(size=2,color='#8ca08c').encode(285 x=alt.X('Day:T',axis=alt.Axis(grid=False)),286 y=alt.Y('Close Public Transport:Q',scale=alt.Scale(domain=[0, 3]),axis=alt.Axis(values=[0,1,2,3], grid=True,gridColor='lightgrey',title='Public Transportation Closure Level')),287 tooltip=[288 alt.Tooltip('Entity:N', title='Country/Region'),289 alt.Tooltip('Day:T', title='Date'),290 alt.Tooltip('Close Public Transport:N', title='Closure Level')291 ]292 ).properties(293 width=400,294 height=300295 ).add_params(296 zoom297 )298 line_chart_transportation += alt.Chart(pd.DataFrame({'Day': [date]})).mark_rule(color='#e377c2').encode(299 x='Day:T'300 )301 ### combine two choros302 final_chart = alt.vconcat(303 alt.hconcat(line_chart_transportation,transportation_choro),304 alt.hconcat(line_chart_category,category_choro)305 )306 return final_chart307## initial map creation308initial_transportation_dashboard = create_transportation_dashboard(date_slider_transportation.value, category_select_transportation.value, subgroup_select_transportation.value)309transportation_dashboard_pane = pn.pane.Vega(initial_transportation_dashboard, width=1000, height=700)310## define a function to update dashboard based on widgets311def update_transportation_dashboard(event):312 new_transportation_dashboard = create_transportation_dashboard(date_slider_transportation.value, category_select_transportation.value, subgroup_select_transportation.value)313 transportation_dashboard_pane.object = new_transportation_dashboard314## bind the widgets to the create_plot function315date_slider_transportation.param.watch(update_transportation_dashboard, 'value')316category_select_transportation.param.watch(update_transportation_dashboard, 'value')317subgroup_select_transportation.param.watch(update_transportation_dashboard, 'value')318## arrange widgets and map pane in a layout319dashboard_transportation = pn.Column(320 pn.Row(category_select_transportation,subgroup_select_transportation),321 date_slider_transportation, 322 transportation_dashboard_pane323)324maincol.append(dashboard_transportation)325maincol.append(" ")326maincol.append(" ")327maincol.append(" ")328maincol.append(" ")329maincol.append(" ")330maincol.append("From this visualization, we can develop more insights in the relationship between policies on public transportation and human mobility. We see at the beginning of the global pandemic, many countries/regions have policies such as recommend or forcing closing public transportation systems. Intuitively, we can imagine this will greatly affect people’s activity to transit stations. After taking a detailed look at several different countries (i.e., **United States**, **Spain**, **Brasil**, and **India**), we find out that in most cases a significant decline in mobility for transit stations will occur when the country/region strengthen their public transportation restriction policy. Conversely, if the restrictions are loosened, mobility levels generally return to baseline or even surpass it.")331 332# Visualization 3333maincol.append("### Policy on stay-at-home lockdown")334maincol.append("This interactive dashboard allows us to explore government policies on stay-at-home requirements and its influence on human mobility. For the stay-at-home requirements level, the larger the number, the stricter the requirements are. Particularly, **0** represents <span style='color: #366388;'>**no measures**</span>, **1** represents <span style='color: #fceb8c;'>**recommended not to leave the house**</span>, **2** represents <span style='color: #f07f59;'>**required to not leave the house with exceptions for daily exercise, grocery shopping, and ‘essential’ trips**</span>, and **3** represents <span style='color: #d26e66;'>**Required to not leave the house with minimal exceptions (e.g. allowed to leave only once every few days, or only one person can leave at a time, etc.)**</span>. ")335## create widgets for interaction336category_select_stay_home = pn.widgets.Select(name='Category', options=worldwide_data['Category'].unique().tolist(), value=worldwide_data['Category'].unique()[0])337date_slider_stay_home = pn.widgets.DateSlider(name='Day', start=worldwide_data['Day'].min(), end=worldwide_data['Day'].max(), value=worldwide_data['Day'].min())338subgroup_select_stay_home = pn.widgets.Select(name='Country/Region', options=worldwide_data['Entity'].unique().tolist(), value='United States')339## define the function to create dashboard340def create_stay_home_dashboard(date, category, subgroup):341 ### date to datetime342 date = pd.to_datetime(date)343 ### zoom condition for line charts344 zoom = alt.selection_interval(bind='scales', encodings=['x'], name="zoom")345 ### color dictionary346 category_colors = {347 'Retail & Recreation': '#17becf',348 'Grocery & Pharmacy': '#1f77b4',349 'Parks': '#ff7f0e',350 'Transit Stations': '#2ca02c',351 'Workplaces': '#bcbd22',352 'Residential': '#d62728'353 }354 choro_colors = {355 'Retail & Recreation': 'blueorange',356 'Grocery & Pharmacy': 'brownbluegreen',357 'Parks': 'purplegreen',358 'Transit Stations': 'pinkyellowgreen',359 'Workplaces': 'purpleorange',360 'Residential': 'redblue'361 }362 current_category_color = category_colors.get(category, 'black')363 current_choro_color = choro_colors.get(category, 'redyellowgreen')364 ###--------------------category choro part--------------------365 ### filter the data366 filtered_data_category_choro = worldwide_data[(worldwide_data['Day'] == date) & (worldwide_data['Category'] == category)]367 ### create the base for category choro368 base_category = alt.Chart(alt.topo_feature(world_json_url, 'countries')).mark_geoshape(369 stroke='black', strokeWidth=0.5370 ).properties(371 width=600,372 height=300373 ).project('naturalEarth1')374 ### create the category choro375 data_layer = base_category.transform_lookup(376 lookup='id',377 from_=alt.LookupData(filtered_data_category_choro, 'ID', ['Entity', 'Category', 'Percentage']),378 ).encode(379 color=alt.Color('Percentage:Q', 380 scale=alt.Scale(domain=[-100, 100], scheme=current_choro_color, reverse=True),381 legend=alt.Legend(title='Change from baseline', orient='right')382 ),383 tooltip=[384 alt.Tooltip('Entity:N', title='Country'),385 alt.Tooltip('Category:N', title='Category'),386 alt.Tooltip('Day:T', title='Date'),387 alt.Tooltip('Percentage:Q', title='Change from baseline(%)', format=".2f")388 ],389 stroke=alt.condition(alt.datum.Entity == subgroup, alt.value('red'),alt.value('black'))390 )391 ### create another background layer392 background = alt.Chart(alt.topo_feature(world_json_url, 'countries')).mark_geoshape(393 fill='lightgray', stroke='black', strokeWidth=0.5394 ).properties(395 width=600,396 height=300397 ).project(398 'naturalEarth1'399 )400 ### combine the choro and the background layer401 category_choro = alt.layer(background, data_layer).resolve_scale(color='independent')402 ###--------------------stay home choro part--------------------403 ### filter the data404 filtered_data_stay_home_choro = stay_home_data[(stay_home_data['Day'] == date)]405 ### create the base for stay home choro406 base_stay_home = alt.Chart(alt.topo_feature(world_json_url, 'countries')).mark_geoshape(407 stroke='black', strokeWidth=0.5408 ).properties(409 width=600,410 height=300411 ).project('naturalEarth1')412 ### create the stay home choro413 stay_home_choro = base_stay_home.transform_lookup(414 lookup='id',415 from_=alt.LookupData(filtered_data_stay_home_choro, 'ID', ['Entity','Stay Home Requirements']),416 default='No Data'417 ).encode(418 color=alt.Color('Stay Home Requirements:N', scale=alt.Scale(domain=[0, 1, 2, 3, 'No Data'],419 range=["#366388", "#fceb8c", "#f07f59", "#d26e66", "#d3d3d3"]),420 legend=alt.Legend(title='Stay-at-home requirements level', orient='right', offset=20)),421 tooltip=[alt.Tooltip('Entity:N', title='Country/Region'), alt.Tooltip('Day:T', title='Date'), alt.Tooltip('Stay Home Requirements:N', title='Stay-at-home requirements level')],422 stroke=alt.condition(alt.datum.Entity == subgroup, alt.value('red'),alt.value('black'))423 )424 ###--------------------category line part--------------------425 filtered_data_category_line = worldwide_data[(worldwide_data['Entity'] == subgroup) & (worldwide_data['Category'] == category)]426 line_chart_category = alt.Chart(filtered_data_category_line).mark_area(color=current_category_color).encode(427 x=alt.X('Day:T',axis=alt.Axis(grid=False)),428 y=alt.Y('Percentage:Q',scale=alt.Scale(domain=[-200, 200]),axis=alt.Axis(values=[-200, -150, -100, -50, 0, 50, 100, 150, 200],grid=True,gridColor='lightgrey',title='Change from baseline (%)')),429 tooltip=[430 alt.Tooltip('Entity:N', title='Country'),431 alt.Tooltip('Category:N', title='Category'),432 alt.Tooltip('Day:T', title='Date'),433 alt.Tooltip('Percentage:Q', title='Change from baseline(%)', format=".2f")434 ]435 ).properties(436 width=400,437 height=300438 ).add_params(439 zoom440 )441 line_chart_category += alt.Chart(pd.DataFrame({'Day': [date]})).mark_rule(color='#e377c2').encode(442 x='Day:T'443 )444 ###--------------------stay home line part--------------------445 filtered_data_stay_home_line = stay_home_data[(stay_home_data['Entity'] == subgroup)]446 line_chart_stay_home = alt.Chart(filtered_data_stay_home_line).mark_line(size=2,color='#366388').encode(447 x=alt.X('Day:T',axis=alt.Axis(grid=False)),448 y=alt.Y('Stay Home Requirements:Q',scale=alt.Scale(domain=[0, 4]),axis=alt.Axis(values=[0,1,2,3,4], grid=True,gridColor='lightgrey',title='Stay-at-home Requirements Level')),449 tooltip=[450 alt.Tooltip('Entity:N', title='Country/Region'),451 alt.Tooltip('Day:T', title='Date'),452 alt.Tooltip('Stay Home Requirements:N', title='Stay-at-home Requirements Level')453 ]454 ).properties(455 width=400,456 height=300457 ).add_params(458 zoom459 )460 line_chart_stay_home += alt.Chart(pd.DataFrame({'Day': [date]})).mark_rule(color='#e377c2').encode(461 x='Day:T'462 )463 ### combine two choros464 final_chart = alt.vconcat(465 alt.hconcat(line_chart_stay_home,stay_home_choro),466 alt.hconcat(line_chart_category,category_choro)467 )468 return final_chart469## initial dashboard creation470initial_stay_home_dashboard = create_stay_home_dashboard(date_slider_stay_home.value, category_select_stay_home.value, subgroup_select_stay_home.value)471stay_home_dashboard_pane = pn.pane.Vega(initial_stay_home_dashboard, width=1000, height=700)472## define a function to update dashboard based on widgets473def update_stay_home_dashboard(event):474 new_stay_home_dashboard = create_stay_home_dashboard(date_slider_stay_home.value, category_select_stay_home.value, subgroup_select_stay_home.value)475 stay_home_dashboard_pane.object = new_stay_home_dashboard476## bind the widgets to the create_plot function477date_slider_stay_home.param.watch(update_stay_home_dashboard, 'value')478category_select_stay_home.param.watch(update_stay_home_dashboard, 'value')479subgroup_select_stay_home.param.watch(update_stay_home_dashboard, 'value')480## arrange widgets and dashboard pane in a layout481dashboard_stay_home = pn.Column(482 pn.Row(category_select_stay_home,subgroup_select_stay_home),483 date_slider_stay_home,484 stay_home_dashboard_pane485)486maincol.append(dashboard_stay_home)487maincol.append(" ")488maincol.append(" ")489maincol.append(" ")490maincol.append(" ")491maincol.append(" ")492maincol.append("Generally, stay-at-home related policies are considered as *most influencial* since they might have an impact on any category of human mobility. In terms of global trend, we see more and more countries/regions take measures related to stay-home policies after in *early April, 2020*, when global pandemic situation becomes worsen and worsen. Many countries have imposed the highest level requirement at some points, such as **Russia**, **China**, and **India**. The stay-at-home restriction level in most countries/regions reduces to no measures or recommended at around *late March, 2022*. Let's take **United States** as an example to investigate the possible inflence of its stay-at-home restrictions on human mobility. We see that the **required not to leave except for essential activities** began on **March 15, 2020**. After the level reduced to **recommended not to leave**, there is a slight gradually decrease in time spent at home, which dropped from around **10%** to around **4%**. A similar increasing trend can be seen in visits to grocery and pharmacy, retail and recreation, and transit stations. However, such trend is not seen in visits to workplaces, suggesting that people might still prefer working from home at this time. For visits to park, since the increase after the change of stay-home restriction level appears similar season pattern as in 2021, we suppose the change might not caused by policy changes.")493 494# Visualization 4495maincol.append("### Policy on social gatherings")496maincol.append("This interactive dashboard allows us to explore government policies on social gatherings and its influence on human mobility. For the gathering restriction level, the larger the number, the stricter the requirements are. Particularly, **0** represents <span style='color: #e7af6c;'>**no restrictions**</span>, **1** represents <span style='color: #fceb8c;'>**restrictions on very large gatherings (the limit is above 1000 people)**</span>, **2** represents <span style='color: #a1dab4;'>**restrictions on gatherings between 100 to 1000 people**</span>, **3** represents <span style='color: #41b6c4;'>**restrictions on gatherings between 10 to 100 people**</span>, and **4** represents <span style='color: #253494;'>**restrictions on gatherings of less than 10 people**</span>.")497## create widgets for interaction498category_select_gathering = pn.widgets.Select(name='Category', options=worldwide_data['Category'].unique().tolist(), value=worldwide_data['Category'].unique()[0])499date_slider_gathering = pn.widgets.DateSlider(name='Day', start=worldwide_data['Day'].min(), end=worldwide_data['Day'].max(), value=worldwide_data['Day'].min())500subgroup_select_gathering = pn.widgets.Select(name='Country/Region', options=worldwide_data['Entity'].unique().tolist(), value='United States')501## define the function to create dashboard502def create_gathering_dashboard(date, category, subgroup):503 ### date to datetime504 date = pd.to_datetime(date)505 ### zoom condition for line charts506 zoom = alt.selection_interval(bind='scales', encodings=['x'], name="zoom")507 ### color dictionary508 category_colors = {509 'Retail & Recreation': '#17becf',510 'Grocery & Pharmacy': '#1f77b4',511 'Parks': '#ff7f0e',512 'Transit Stations': '#2ca02c',513 'Workplaces': '#bcbd22',514 'Residential': '#d62728'515 }516 choro_colors = {517 'Retail & Recreation': 'blueorange',518 'Grocery & Pharmacy': 'brownbluegreen',519 'Parks': 'purplegreen',520 'Transit Stations': 'pinkyellowgreen',521 'Workplaces': 'purpleorange',522 'Residential': 'redblue'523 }524 current_category_color = category_colors.get(category, 'black')525 current_choro_color = choro_colors.get(category, 'redyellowgreen')526 ###--------------------category choro part--------------------527 ### filter the data528 filtered_data_category_choro = worldwide_data[(worldwide_data['Day'] == date) & (worldwide_data['Category'] == category)]529 ### create the base for category choro530 base_category = alt.Chart(alt.topo_feature(world_json_url, 'countries')).mark_geoshape(531 stroke='black', strokeWidth=0.5532 ).properties(533 width=600,534 height=300535 ).project('naturalEarth1')536 ### create the category choro537 data_layer = base_category.transform_lookup(538 lookup='id',539 from_=alt.LookupData(filtered_data_category_choro, 'ID', ['Entity', 'Category', 'Percentage']),540 ).encode(541 color=alt.Color('Percentage:Q', 542 scale=alt.Scale(domain=[-100, 100], scheme=current_choro_color, reverse=True),543 legend=alt.Legend(title='Change from baseline', orient='right')544 ),545 tooltip=[546 alt.Tooltip('Entity:N', title='Country'),547 alt.Tooltip('Category:N', title='Category'),548 alt.Tooltip('Day:T', title='Date'),549 alt.Tooltip('Percentage:Q', title='Change from baseline(%)', format=".2f")550 ],551 stroke=alt.condition(alt.datum.Entity == subgroup, alt.value('red'),alt.value('black'))552 )553 ### create another background layer554 background = alt.Chart(alt.topo_feature(world_json_url, 'countries')).mark_geoshape(555 fill='lightgray', stroke='black', strokeWidth=0.5556 ).properties(557 width=600,558 height=300559 ).project(560 'naturalEarth1'561 )562 ### combine the choro and the background layer563 category_choro = alt.layer(background, data_layer).resolve_scale(color='independent')564 ###--------------------gathering choro part--------------------565 ### filter the data566 filtered_data_gathering_choro = gathering_data[(gathering_data['Day'] == date)]567 ### create the base for gathering choro568 base_gathering = alt.Chart(alt.topo_feature(world_json_url, 'countries')).mark_geoshape(569 stroke='black', strokeWidth=0.5570 ).properties(571 width=600,572 height=300573 ).project('naturalEarth1')574 ### create the gathering choro575 gathering_choro = base_gathering.transform_lookup(576 lookup='id',577 from_=alt.LookupData(filtered_data_gathering_choro, 'ID', ['Entity','Restriction Gatherings']),578 default='No Data'579 ).encode(580 color=alt.Color('Restriction Gatherings:N', scale=alt.Scale(domain=[0, 1, 2, 3, 4, 'No Data'],581 range=["#e7af6c", "#fceb8c", "#a1dab4", "#41b6c4", "#253494", "#d3d3d3"]),582 legend=alt.Legend(title='Gatherings restriction level')),583 tooltip=[alt.Tooltip('Entity:N', title='Country'), alt.Tooltip('Day:T', title='Date'), alt.Tooltip('Restriction Gatherings:N', title='Level')],584 stroke=alt.condition(alt.datum.Entity == subgroup, alt.value('red'),alt.value('black'))585 )586 ###--------------------category line part--------------------587 filtered_data_category_line = worldwide_data[(worldwide_data['Entity'] == subgroup) & (worldwide_data['Category'] == category)]588 line_chart_category = alt.Chart(filtered_data_category_line).mark_area(color=current_category_color).encode(589 x=alt.X('Day:T',axis=alt.Axis(grid=False)),590 y=alt.Y('Percentage:Q',scale=alt.Scale(domain=[-200, 200]),axis=alt.Axis(values=[-200, -150, -100, -50, 0, 50, 100, 150, 200],grid=True,gridColor='lightgrey',title='Change from baseline (%)')),591 tooltip=[592 alt.Tooltip('Entity:N', title='Country'),593 alt.Tooltip('Category:N', title='Category'),594 alt.Tooltip('Day:T', title='Date'),595 alt.Tooltip('Percentage:Q', title='Change from baseline(%)', format=".2f")596 ]597 ).properties(598 width=400,599 height=300600 ).add_params(601 zoom602 )603 line_chart_category += alt.Chart(pd.DataFrame({'Day': [date]})).mark_rule(color='#e377c2').encode(604 x='Day:T'605 )606 ###--------------------gathering line part--------------------607 filtered_data_gathering_line = gathering_data[(gathering_data['Entity'] == subgroup)]608 line_chart_gathering = alt.Chart(filtered_data_gathering_line).mark_line(size=2,color='#8ca08c').encode(609 x=alt.X('Day:T',axis=alt.Axis(grid=False)),610 y=alt.Y('Restriction Gatherings:Q',scale=alt.Scale(domain=[0, 5]),axis=alt.Axis(values=[0,1,2,3,4,5], grid=True,gridColor='lightgrey',title='Gathering Restrictions Level')),611 tooltip=[612 alt.Tooltip('Entity:N', title='Country/Region'),613 alt.Tooltip('Day:T', title='Date'),614 alt.Tooltip('Restriction Gatherings:N', title='Gathering Restrictions Level')615 ]616 ).properties(617 width=400,618 height=300619 ).add_params(620 zoom621 )622 line_chart_gathering += alt.Chart(pd.DataFrame({'Day': [date]})).mark_rule(color='#e377c2').encode(623 x='Day:T'624 )625 ### combine two choros626 final_chart = alt.vconcat(627 alt.hconcat(line_chart_gathering,gathering_choro),628 alt.hconcat(line_chart_category,category_choro)629 )630 return final_chart631## initial dashboard creation632initial_gathering_dashboard = create_gathering_dashboard(date_slider_gathering.value, category_select_gathering.value, subgroup_select_gathering.value)633gathering_dashboard_pane = pn.pane.Vega(initial_gathering_dashboard, width=1000, height=700)634## define a function to update dashboard based on widgets635def update_gathering_dashboard(event):636 new_gathering_dashboard = create_gathering_dashboard(date_slider_gathering.value, category_select_gathering.value, subgroup_select_gathering.value)637 gathering_dashboard_pane.object = new_gathering_dashboard638## bind the widgets to the create_plot function639date_slider_gathering.param.watch(update_gathering_dashboard, 'value')640category_select_gathering.param.watch(update_gathering_dashboard, 'value')641subgroup_select_gathering.param.watch(update_gathering_dashboard, 'value')642## Arrange widgets and dashboard pane in a layout643dashboard_gathering = pn.Column(644 pn.Row(category_select_gathering,subgroup_select_gathering),645 date_slider_gathering,646 gathering_dashboard_pane647)648maincol.append(dashboard_gathering)649maincol.append(" ")650maincol.append(" ")651maincol.append(" ")652maincol.append(" ")653maincol.append(" ")654maincol.append("The social gathering policy is the type of restrictions that most countries/regions have imposed on a highest level among all kinds of policies we are looking at. It's typical influence on mobility in *Retail & Recreation* are found across many countries. For instance, both **United States** and **Switzerland** strenghthened their restrictions on social gathering from level 3 to 4 **near the end of 2021**, leading to a sharp decrease in visits to retail and recreation places in these two countries, respectively. While for **Turkey**, which experienced a restriction relaxation from level 3 to no measures in *July 2021*, we see a clear trend of increase in visits to retail and recreation places. Similar pattern can also be found in **Canada** after its relaxation of social gathering policy in *April, 2022*.")655maincol.append(pn.layout.Divider())656 657# Simulation658maincol.append("## Understanding the effect of policy restrictions")659## local path for video files660normal_video_path = 'simulation_normal.mp4'661lockdown_video_path = 'simulation_lockdown.mp4'662## Create video panes663normal_video_pane = pn.pane.Video(normal_video_path, width=800, loop=True)664lockdown_video_pane = pn.pane.Video(lockdown_video_path, width=800, loop=True)665maincol.append("After exploring all these interactive dashboards, we are more informed of what influence might various policies have on people’s mobility. However, there is an additional question pending: **why these restrictions are helpful in terms of disease control?**")666maincol.append("To fully understand the mechanism requires rich knowledge in COVID-19 transmission and public health. However, it is definitely not difficult to get an intuition. Therefore, let’s take a look at some simple simulations to better understand why these policies can be helpful.")667maincol.append("""668Imagine a small town with **2000** residents. We assume the following conditions to simulate the spead of a hypothetical disease in this town:669* Population’s age follows a **gaussian distribution** with a mean **55**, stand deviation of **1/3 the mean**, and max of **105**.670* There is a **3%** chance of becoming infected when being around an infected person.671* The disease might lead to mortality with a baseline rate **2%**. Mortality chances start increasing at age 55, going up exponentially up to **10%** at age 75 and above.672* The healthcare capacity is **300 beds** in this town.673* When in medical treatment, mortality chance is **halved**. Otherwise, mortality chance **increases threefold**.674""")675## Simulation for no measures676maincol.append("### Simulation for no measures")677maincol.append("We first take a look at what will happen if no measures are taken at all, which means people will act as usual. Notice how the slope of the red curve, which represents the number of infected people, rises rapidly as the disease spreads. In addition, the healthcare system becomes completely overwhelmed, leading to **60 fatalities** (**3%** of the population). ")678maincol.append(normal_video_pane)679maincol.append("### Simulation for lock-down")680maincol.append("What if all conditions are the same, but to simulate people staying at home whenever possible and only going out when they have to? ")681maincol.append("Let's simulate a lock-down once **5%** of the population get infected. To simulate this, we will make **90% of the residents stop moving when the lock-down begins**, while **the remaining 10% move with substantially reduced speed** to simulate them being more cautious. This 10% represents the professions that are considered critical to society: these people will still be on the move and in contact with other people even under a lock-down policy. Another part of this 10% comes from people being people, meaning no lock-down will be perfect as there will always be those breaking quarantine. Notice that once the policy begins, the number of infections still increases for some time. This happens because of some of the healthy people will be locked into the same household with infected people, and thus become infected relatively quickly as well. If one of the moving population members (perhaps someone delivering groceries or packages) infects one of a cluster of people locked down together, the disease might spread. However, the general situation is much better than the situation when no actions are taken. The total infected people does not exceed the healthcare system's capacity, and the fatalites reduces to **8**, which is only **0.4%** of the population.")682maincol.append(lockdown_video_pane)683maincol.append(pn.layout.Divider())684maincol.append("## Conclusion")685maincol.append("In conclusion, through interactive visualizations above, we observe significant trends, such as increased time spent at residential locations and decreases in visits to other mobility categories during the global pandemic. We also make comparative analysis on possible influence of policy changes on certain type of human mobility. Except for the examples mentioned above, you are highly encouraged to use the above visual tools to explore the data by adjusting selection conditions and viewing the outcomes.")686maincol.append("However, these visualizations have their limitations. We do not employ rigorous statistical tests to confirm the observed correlations between policy implementations and changes in human mobility, meaning the findings should be interpreted with caution. Furthermore, the simulations presented to illustrate potential disease spread and control under a lock-down policy are based on a hypothetical setting, which simplifies many complex factors encountered in real-world situations. These models do not account for all demographic, social, and geographical variables that can influence disease transmission and the effectiveness of policy measures.")687maincol.append("Understanding these impacts and the dynamics of mobility during the pandemic is crucial for better preparation and response in future health crises. For those looking to explore this topic further, we refer you to additional resources include the [Johns Hopkins Coronavirus Resource Center](https://coronavirus.jhu.edu) and the [World Health Organization's COVID-19 dashboard](https://data.who.int/dashboards/covid19) for more comprehensive data and in-depth analysis. Academic journals and publications related to epidemiology and public health policy also offer more detailed studies and findings of the pandemic's broader socio-economic effects and how policy measures contribute to controlling disease spread.")688# Finish the application689template.main.append(maincol)690template.servable(title="COVID-19: Policy Responses and Google Mobility Trends")