Last active
August 19, 2020 20:19
-
-
Save edasmalchi/e24cd4ffa402fc88047fc94cd9733592 to your computer and use it in GitHub Desktop.
transit-supply-viz
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: pd1 | |
channels: | |
- conda-forge | |
- defaults | |
dependencies: | |
- pandas | |
- jupyter | |
- matplotlib | |
- appmode | |
- xlrd | |
- geopandas | |
- descartes | |
- folium | |
- ipyleaflet | |
- pysal | |
prefix: /Users/edasmalchi/anaconda3/envs/pd1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import geopandas as gpd\n", | |
"import pandas as pd\n", | |
"idx = pd.IndexSlice\n", | |
"import numpy as np\n", | |
"\n", | |
"# import folium\n", | |
"from folium.plugins import MarkerCluster\n", | |
"import pysal as ps\n", | |
"from pysal.viz import mapclassify\n", | |
"\n", | |
"import ipywidgets as widgets" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"#Patch for Chrome courtesy of : https://github.com/python-visualization/folium/issues/812#issuecomment-555238062\n", | |
"\n", | |
"import base64\n", | |
"\n", | |
"import folium\n", | |
"\n", | |
"def _repr_html_(self, **kwargs):\n", | |
" html = base64.b64encode(self.render(**kwargs).encode('utf8')).decode('utf8')\n", | |
" onload = (\n", | |
" 'this.contentDocument.open();'\n", | |
" 'this.contentDocument.write(atob(this.getAttribute(\\'data-html\\')));'\n", | |
" 'this.contentDocument.close();'\n", | |
" )\n", | |
" if self.height is None:\n", | |
" iframe = (\n", | |
" '<div style=\"width:{width};\">'\n", | |
" '<div style=\"position:relative;width:100%;height:0;padding-bottom:{ratio};\">'\n", | |
" '<iframe src=\"about:blank\" style=\"position:absolute;width:100%;height:100%;left:0;top:0;'\n", | |
" 'border:none !important;\" '\n", | |
" 'data-html={html} onload=\"{onload}\" '\n", | |
" 'allowfullscreen webkitallowfullscreen mozallowfullscreen>'\n", | |
" '</iframe>'\n", | |
" '</div></div>').format\n", | |
" iframe = iframe(html=html, onload=onload, width=self.width, ratio=self.ratio)\n", | |
" else:\n", | |
" iframe = ('<iframe src=\"about:blank\" width=\"{width}\" height=\"{height}\"'\n", | |
" 'style=\"border:none !important;\" '\n", | |
" 'data-html={html} onload=\"{onload}\" '\n", | |
" '\"allowfullscreen\" \"webkitallowfullscreen\" \"mozallowfullscreen\">'\n", | |
" '</iframe>').format\n", | |
" iframe = iframe(html=html, onload=onload, width=self.width, height=self.height)\n", | |
" return iframe\n", | |
"\n", | |
"folium.branca.element.Figure._repr_html_ = _repr_html_" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"la_county = gpd.read_file(\"la_county.geojson\", driver='GeoJSON')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# service_data = pd.read_parquet('service_data_mdf.parquet')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"service_data = pd.read_json('with_new_dot_mdf.json', orient='table')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def select_view(df_all, agencies, service_type, geo):\n", | |
" serv_types = ['am_peak', 'midday', 'pm_peak', 'evening', 'early_am', 'total']\n", | |
" \n", | |
" assert service_type in serv_types\n", | |
" \n", | |
" agency_filtered = df_all\n", | |
" if 'all_agencies' not in agencies:\n", | |
" agency_filtered = df_all.loc[idx[:, :, agencies], :]\n", | |
" \n", | |
" agency_summed = agency_filtered.groupby(level=['tract', 'covid']).sum()\n", | |
" agency_summed = agency_summed[[f'{service_type}_vrh']]\n", | |
" \n", | |
" pre_covid = agency_summed.loc[idx[:, 0], :].reset_index(level='covid', drop=True)\n", | |
" covid = agency_summed.loc[idx[:, 1], :].reset_index(level='covid', drop=True)\n", | |
" difference = covid - pre_covid\n", | |
" \n", | |
" pre_covid = pre_covid.rename(\n", | |
" columns={pre_covid.columns[0]:f'{pre_covid.columns[0]}_pre_covid'})\n", | |
" covid = covid.rename(\n", | |
" columns={covid.columns[0]:f'{covid.columns[0]}_covid'})\n", | |
" difference = difference.rename(\n", | |
" columns={difference.columns[0]:f'{difference.columns[0]}_difference'})\n", | |
" joined = pre_covid.join(covid).join(difference)\n", | |
" joined['pct_maintained'] = joined.iloc[:, 2] / joined.iloc[:, 0] + 1\n", | |
" return geo.set_index('tract').join(joined.dropna(), how='inner')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def add_choropleth(vrh_gdf, m, classifier):\n", | |
" \n", | |
" vrh_gdf = vrh_gdf[vrh_gdf['pct_maintained'] != np.inf]\n", | |
" vrh_gdf = vrh_gdf[vrh_gdf['pct_maintained'] <= 2.0]\n", | |
" vrh_gdf['pct_maintained'] = vrh_gdf['pct_maintained'] * 100\n", | |
" \n", | |
" if classifier == 'Fixed':\n", | |
"# threshold_scale = [0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 2.0]\n", | |
" threshold_scale = [0, 20, 40, 60, 80, 100, 120, 200]\n", | |
" elif classifier == 'Quantiles':\n", | |
" threshold_scale = mapclassify.Quantiles(\n", | |
" vrh_gdf['pct_maintained'], k = 5).bins.tolist()\n", | |
" threshold_scale = [vrh_gdf['pct_maintained'].min()] + threshold_scale\n", | |
" elif classifier == 'Natural Breaks':\n", | |
" threshold_scale = mapclassify.NaturalBreaks(\n", | |
" vrh_gdf['pct_maintained'], k = 5).bins.tolist()\n", | |
" threshold_scale = [vrh_gdf['pct_maintained'].min()] + threshold_scale\n", | |
"# print(threshold_scale)\n", | |
" choropleth = folium.Choropleth(geo_data = vrh_gdf.reset_index().to_json(),\n", | |
" data = vrh_gdf.reset_index(),\n", | |
" columns = ('tract', 'pct_maintained'), key_on = 'feature.properties.tract',\n", | |
" nan_fill_color = 'red', fill_color = 'YlGnBu', fill_opacity = 0.6, line_opacity = 0.2, \n", | |
" threshold_scale = threshold_scale, legend_name='Service Maintained Post-COVID (Percentage)'\n", | |
" )\n", | |
" choropleth.add_to(m)\n", | |
" return" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"serv_list = [x[:-4] for x in list(service_data.columns)] \n", | |
"\n", | |
"disp_serv = [i.replace('_', ' ').capitalize() for i in serv_list]\n", | |
"\n", | |
"disp_serv = [i.replace('Am', 'AM').replace('Pm', 'PM').replace('am', 'AM').replace('peak', 'Peak') for i in disp_serv]\n", | |
"\n", | |
"disp_to_serv = dict(zip(disp_serv, serv_list))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"agency_list = ['all_agencies'] + list(service_data.droplevel(['tract', 'covid']).index.unique())\n", | |
"\n", | |
"disp_agency_la = ['All Agencies', 'LA Metro (bus)', 'LADOT Transit', 'LA Metro (rail)', 'Torrance Transit',\n", | |
" 'Santa Monica Big Blue Bus', 'Culver CityBus', 'Long Beach Transit',\n", | |
" 'Palos Verdes Peninsula Transit Authority', 'Norwalk Transit', 'Pasadena Transit']\n", | |
"\n", | |
"disp_to_agency = dict(zip(disp_agency_la, agency_list))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"region_widget = widgets.RadioButtons(\n", | |
" options=['pepperoni', 'pineapple', 'anchovies'],\n", | |
"# value='pineapple', # Defaults to 'pineapple'\n", | |
"# layout={'width': 'max-content'}, # If the items' names are long\n", | |
" description='Pizza topping:',\n", | |
" disabled=False\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"agency_widget = widgets.SelectMultiple(\n", | |
" options=disp_agency_la,\n", | |
" value=[disp_agency_la[0]],\n", | |
" #rows=10,\n", | |
" description='Agencies',\n", | |
" disabled=False\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 14, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"service_widget = widgets.Select(\n", | |
" options=disp_serv,\n", | |
" value=disp_serv[0],\n", | |
" #rows=10,\n", | |
" description='Service Type',\n", | |
" disabled=False\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 15, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"classify_widget = widgets.RadioButtons(\n", | |
" options=['Quantiles', 'Natural Breaks', 'Fixed'],\n", | |
" value='Fixed', # Defaults to 'Fixed'\n", | |
"# layout={'width': 'max-content'}, # If the items' names are long\n", | |
" description='Classifier:',\n", | |
" disabled=False\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def regional_map(region, agencies, service_type):\n", | |
" return" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 17, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def interactive_map(agencies, service_type, classifier):\n", | |
" print('Running data query...', end='')\n", | |
"# print(service_type)\n", | |
" #convert displayed names to short names\n", | |
" agencies = [disp_to_agency[agency] for agency in agencies]\n", | |
" service_type = disp_to_serv[service_type]\n", | |
" view = select_view(service_data, agencies, service_type, la_county)\n", | |
" m = folium.Map([33.9556, -118.4399], zoom_start = 10)\n", | |
" add_choropleth(view, m, classifier)\n", | |
" print(' Done!')\n", | |
" print('Drawing map...')\n", | |
" display(m)\n", | |
" return m" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 18, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"w = widgets.interactive_output(\n", | |
" interactive_map,\n", | |
" {'agencies': agency_widget, 'service_type': service_widget,\n", | |
" 'classifier': classify_widget})\n", | |
"ui = widgets.VBox([\n", | |
" widgets.HBox([agency_widget, service_widget]), \n", | |
" classify_widget])" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Visualizing Transit Service Supply During the Pandemic Response\n", | |
"\n", | |
"This tool visualizes an estimate of how the supply of transit service (measured as service hours) in each Census tract has changed since the start of the COVID-19 pandemic. Using GTFS data for each operator, it compares service levels from the most recent data available before March 2020 to the most recent data available from after mid-March 2020.\n", | |
"\n", | |
"By default, it shows an aggregation of all transit agencies in a region for which suitable data were available, but the selection boxes allow a custom subset of operators.\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 19, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"application/vnd.jupyter.widget-view+json": { | |
"model_id": "a96792a5ddab411aa2027f0ea028e8e2", | |
"version_major": 2, | |
"version_minor": 0 | |
}, | |
"text/plain": [ | |
"VBox(children=(HBox(children=(SelectMultiple(description='Agencies', index=(0,), options=('All Agencies', 'LA …" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
}, | |
{ | |
"data": { | |
"application/vnd.jupyter.widget-view+json": { | |
"model_id": "fbafc70cb89e43b686b857dab31e60bf", | |
"version_major": 2, | |
"version_minor": 0 | |
}, | |
"text/plain": [ | |
"Output(outputs=({'output_type': 'stream', 'text': 'Running data query...', 'name': 'stdout'},))" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"display(ui, w)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.8.5" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 4 | |
} |
View raw
(Sorry about that, but we can’t show files that are this big right now.)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment