import warnings
warnings.filterwarnings("ignore")
import altair as alt
import branca.colormap as cm
import folium
import geopandas as gpd
import gcsfs
import google.auth
import pandas as pd
from dotenv import dotenv_values
from IPython.display import HTML, display
import world_cup_vars as wc_vars
import event_helpers
import create_route_and_stop_event_summary as prep_data
import chart_utils
import poi_geography_utils as poi_geog_utils
credentials, _ = google.auth.default()
GCS_FILE_PATH = wc_vars.GCS_FILE_PATH
CARTO_TILE_URL = dotenv_values("../_env")["CARTO_TILE_URL"]
STADIUM_NAME = "levi"
STADIUM_FULL_NAME = "Levi's Stadium"
alt.data_transformers.enable("vegafusion")DataTransformerRegistry.enable('vegafusion')# Set drop down menu to be on the upper right for the charts
display(
HTML(
"""
<style>
form.vega-bindings {
position: absolute;
right: 0px;
top: 0px;
}
</style>
"""
)
)Loading...
Regional Trips¶
# daily schedule + RT route-direction summary
daily_trips = prep_data.prep_fct_daily_schedule_rt_route_direction_summary(
wc_vars.event_name,
wc_vars.bay_area_names,
wc_vars.levi_match_times,
)
# special routes are tagged, filter for these and merge
routes_near_poi = prep_data.import_routes_near_poi(
wc_vars.event_name, STADIUM_NAME
).query(
'is_special_route==True'
)
levi_trips = pd.merge(
daily_trips,
routes_near_poi,
on = ["feed_key", "schedule_name", "route_id", "direction_id", "route_name"],
how = "inner"
)daily_trips_by_operator = event_helpers.aggregate_daily_trips(
levi_trips, ["service_date", "schedule_name"])chart_utils.trip_chart_with_event_dates(
daily_trips_by_operator, wc_vars.levi_dates, color_col="schedule_name"
).properties(
title= "Daily Trips by Operator during World Cup",
width=500, height=300
)Loading...
Trips by Route¶
daily_trips_by_route = event_helpers.aggregate_daily_trips(
levi_trips, ["service_date", "schedule_name", "route_name"]
)chart_utils.trip_chart_with_event_dates(
daily_trips_by_route, wc_vars.levi_dates, color_col="route_name"
).properties(
title= "Daily Trips by Route during World Cup",
width=500, height=300
)Loading...
# Get each route's daily average number of trips on event days vs non-event days - plot on map
route_gdf = gpd.read_parquet(
f"{GCS_FILE_PATH}route_summary_{STADIUM_NAME}.parquet",
storage_options = {"token": credentials.token}
)
stop_gdf = gpd.read_parquet(
f"{GCS_FILE_PATH}stop_summary_{STADIUM_NAME}.parquet",
storage_options = {"token": credentials.token}
)
poi = gpd.read_parquet(
f"{wc_vars.GCS_FILE_PATH}points_of_interest_{wc_vars.event_name}.parquet",
storage_options = {"token": credentials.token},
filters = [[("point_of_interest", "==", STADIUM_FULL_NAME)]]
)# check that our change column works with the shared legend cutoff across the maps in this notebook
#route_gdf.combined_change_daily_trips.min(), route_gdf.combined_change_daily_trips.max()# check that our change column works with the shared legend cutoff across the maps in this notebook
#stop_gdf.combined_change_daily_arrivals.min(), stop_gdf.combined_change_daily_arrivals.max()from _color_palette import SERVICE_CHANGE_COLORS, SERVICE_CHANGE_CAPTION
SERVICE_CHANGE_INDEX = [-200, -100, -1, 1, 50, 100, 200, 300, 800]route_map = route_gdf[
(route_gdf.is_route_near == True) | (route_gdf.is_special_route == True)
].explore(
"combined_change_daily_trips",
tiles = CARTO_TILE_URL,
attr="(C) OpenStreetMap contributors (C) CARTO",
cmap = cm.StepColormap(
colors=SERVICE_CHANGE_COLORS,
index=SERVICE_CHANGE_INDEX,
vmin=route_gdf.combined_change_daily_trips.min(),
vmax=route_gdf.combined_change_daily_trips.max(),
tick_labels=SERVICE_CHANGE_INDEX,
caption=SERVICE_CHANGE_CAPTION
),
style_kwds = {
"weight": 3
},
name = "Additional Trips (compared to baseline) on Special Service Routes"
)
# keep stops that are on routes that got near
route_map = stop_gdf[
(stop_gdf.is_route_near == True) | (stop_gdf.is_special_route == True)
].drop(columns = ["near_route_ids", "special_route_ids"]).explore(
"combined_change_daily_arrivals",
m = route_map,
cmap = cm.StepColormap(
colors=SERVICE_CHANGE_COLORS,
index=SERVICE_CHANGE_INDEX,
vmin=stop_gdf.combined_change_daily_arrivals.min(),
vmax=stop_gdf.combined_change_daily_arrivals.max(),
tick_labels=SERVICE_CHANGE_INDEX,
caption=SERVICE_CHANGE_CAPTION
),
style_kwds={
"style_function": lambda x: {
"radius": x["properties"]["combined_change_daily_arrivals"]*0.05,
"fillOpacity": 0.5,
#"color": "#808080", # stroke color, if defined, stroke color will not be the color of the marker
"strokeOpacity": 1,
"weight": 2,
#"weight":x["properties"]["combined_change_daily_arrivals"]*0.08, # weight seems to be stroke weight
}
},
name = "Additional Arrivals (compared to baseline)"
)
# https://python-visualization.github.io/folium/latest/user_guide/geojson/geojson_marker.html
# available colors, https://python-visualization.github.io/folium/latest/reference.html#folium.map.Marker
route_map = poi.explore(
"point_of_interest",
m=route_map,
marker_type = "marker",
name=STADIUM_FULL_NAME,
legend = False, # legend color is blue no matter what color is set for icon, confusing
marker_kwds=dict(icon=folium.Icon(icon="star", color="darkpurple")),
)
folium.LayerControl().add_to(route_map)
route_mapLoading...
Stop Arrivals¶
# filter to stops near stadium
stops_near_gdf = stop_gdf[(stop_gdf.is_stop_near == True) &
(stop_gdf.is_special_route == True)]operator_df = (
stops_near_gdf
.groupby(["schedule_name", "route_id_array", "stop_name"])
.agg({
"change_daily_arrivals_weekday": "sum",
"change_daily_arrivals_weekend": "sum",
"stop_id": "nunique"
})
.reset_index()
.rename(columns = {"stop_id": "n_stop_ids"})
)for i in sorted(operator_df.schedule_name.unique()):
chart = chart_utils.weekday_weekend_chart_by_operator(operator_df, i)
display(chart)Loading...
Loading...
Loading...
Loading...
Loading...
Arrivals by Time-of-Day¶
We want to see how service frequency varies on match days, particularly for travelers arriving and leaving the stadium.
We will compare the stop’s arrivals_per_hour against a typical non-event day for stops within 1 mile of the stadium.
Weekend Mid-Day Matches¶
Pre-match travel:
am_peakPost-match travel:
pm_peakNon-event comparison: weekends for
am_peak,midday,pm_peak
stop_tod_df = pd.read_parquet(
f"{GCS_FILE_PATH}arrivals_time_of_day_summary_{STADIUM_NAME}.parquet",
filesystem = gcsfs.GCSFileSystem()
)
stop_tod_near = poi_geog_utils.keep_stops_within_buffer(
stop_gdf,
stop_tod_df,
poi,
)stop_tod_near2 = (
stop_tod_near
.groupby(["service_date", "schedule_name", "time_of_day", "day_type", "route_id_array"], dropna=False)
.agg({
"arrivals_per_hour": "mean",
"stop_id": "count"
}).reset_index().rename(columns = {"stop_id": "n_stops"})
)
stop_tod_near2 = stop_tod_near2.assign(
arrivals_per_hour = stop_tod_near2.arrivals_per_hour.round(2)
)
list_of_operators = sorted(stop_tod_near2.schedule_name.unique())MATCH_TOD = "midday"
MATCH_DAY_TYPE = "weekend"
filtered_levi_dict = event_helpers.grab_matches_by_day_type(
wc_vars.levi_match_times, MATCH_TOD, MATCH_DAY_TYPE)
MATCH_DAYS = list(filtered_levi_dict.keys())
operator_dropdown = alt.binding_select(
options=list_of_operators,
name="GTFS Schedule: ",
)
dropdown_param = alt.selection_point(
fields=["schedule_name"], value=list_of_operators[0], bind=operator_dropdown
)
selection = alt.selection_point(fields=["route_id_array"], bind="legend")
indiv_time_of_day_charts = [
chart_utils.arrivals_by_time_of_day_grouped_bar(
stop_tod_near2[stop_tod_near2.service_date.isin(["nonevent"] + MATCH_DAYS)],
time_of_day,
MATCH_DAY_TYPE
).properties(
title = f"{time_of_day}", width=200, height=100
).add_params(selection, dropdown_param).transform_filter(dropdown_param)
for time_of_day in ["am_peak", "midday", "pm_peak", "early_am", "evening", "owl"]
# set time-of-day order to put times surrounding match at the top
]
alt.vconcat(
alt.hconcat(*indiv_time_of_day_charts[:3]).resolve_scale(y ='shared'),
alt.hconcat(*indiv_time_of_day_charts[3:]).resolve_scale(y ='shared')
).add_params(selection, dropdown_param).properties(
title={
"text": f"Levi's {MATCH_DAY_TYPE.title()} Matches on {', '.join(MATCH_DAYS)}",
"subtitle": [f"Match Starts: {MATCH_TOD}", "Travel Changes: am_peak, midday, pm_peak"]
}).resolve_scale(y ='shared')Loading...
Weekday PM Peak Matches¶
Pre-match travel:
middayPost-match travel:
eveningNon-event comparison: weekday for
midday,pm_peak,evening
MATCH_TOD = "pm_peak"
MATCH_DAY_TYPE = "weekday"
filtered_levi_dict = event_helpers.grab_matches_by_day_type(
wc_vars.levi_match_times, MATCH_TOD, MATCH_DAY_TYPE)
MATCH_DAYS = list(filtered_levi_dict.keys())
operator_dropdown = alt.binding_select(
options=list_of_operators,
name="GTFS Schedule: ",
)
dropdown_param = alt.selection_point(
fields=["schedule_name"], value=list_of_operators[0], bind=operator_dropdown
)
selection = alt.selection_point(fields=["route_id_array"], bind="legend")
indiv_time_of_day_charts = [
chart_utils.arrivals_by_time_of_day_grouped_bar(
stop_tod_near2[stop_tod_near2.service_date.isin(["nonevent"] + MATCH_DAYS)],
time_of_day,
MATCH_DAY_TYPE
).properties(
title = f"{time_of_day}", width=200, height=100
).add_params(selection, dropdown_param).transform_filter(dropdown_param)
for time_of_day in ["midday", "pm_peak", "evening", "early_am", "am_peak", "owl"]
# set time-of-day order to put times surrounding match at the top
]
alt.vconcat(
alt.hconcat(*indiv_time_of_day_charts[:3]).resolve_scale(y ='shared'),
alt.hconcat(*indiv_time_of_day_charts[3:]).resolve_scale(y ='shared')
).add_params(selection, dropdown_param).properties(
title={
"text": f"Levi's {MATCH_DAY_TYPE.title()} Matches on {', '.join(MATCH_DAYS)}",
"subtitle": [f"Match Starts: {MATCH_TOD}", "Travel Changes: midday, pm_peak, evening"]
}).resolve_scale(y ='shared')Loading...
Weekday Evening Matches¶
Pre-match travel:
pm_peakPost-match travel:
owlNon-event comparison: weekday for
pm_peak,evening,owl
MATCH_TOD = "evening"
MATCH_DAY_TYPE = "weekday"
filtered_levi_dict = event_helpers.grab_matches_by_day_type(
wc_vars.levi_match_times, MATCH_TOD, MATCH_DAY_TYPE)
MATCH_DAYS = list(filtered_levi_dict.keys())
operator_dropdown = alt.binding_select(
options=list_of_operators,
name="GTFS Schedule: ",
)
dropdown_param = alt.selection_point(
fields=["schedule_name"], value=list_of_operators[0], bind=operator_dropdown
)
selection = alt.selection_point(fields=["route_id_array"], bind="legend")
indiv_time_of_day_charts = [
chart_utils.arrivals_by_time_of_day_grouped_bar(
stop_tod_near2[stop_tod_near2.service_date.isin(["nonevent"] + MATCH_DAYS)],
time_of_day,
MATCH_DAY_TYPE
).properties(
title = f"{time_of_day}", width=200, height=100
).add_params(selection, dropdown_param).transform_filter(dropdown_param)
for time_of_day in ["pm_peak", "evening", "owl", "early_am", "am_peak", "midday"]
# set time-of-day order to put times surrounding match at the top
]
alt.vconcat(
alt.hconcat(*indiv_time_of_day_charts[:3]).resolve_scale(y ='shared'),
alt.hconcat(*indiv_time_of_day_charts[3:]).resolve_scale(y ='shared')
).add_params(selection, dropdown_param).properties(
title={
"text": f"Levi's {MATCH_DAY_TYPE.title()} Matches on {', '.join(MATCH_DAYS)}",
"subtitle": [f"Match Starts: {MATCH_TOD}", "Travel Changes: pm_peak, evening, owl"]
}).resolve_scale(y ='shared')Loading...