diff --git a/_shared_utils/setup.py b/_shared_utils/setup.py index 96ac7b7102..7a3800d9f2 100644 --- a/_shared_utils/setup.py +++ b/_shared_utils/setup.py @@ -3,7 +3,7 @@ setup( name="shared_utils", packages=find_packages(), - version="2.2.1", + version="2.2.2", description="Shared utility functions for data analyses", author="Cal-ITP", license="Apache", diff --git a/_shared_utils/shared_utils/__init__.py b/_shared_utils/shared_utils/__init__.py index 4545e76502..ac36a8ec16 100644 --- a/_shared_utils/shared_utils/__init__.py +++ b/_shared_utils/shared_utils/__init__.py @@ -7,10 +7,10 @@ rt_dates, rt_utils, schedule_rt_utils, + utils_to_add, ) __all__ = [ - # "calitp_color_palette", "dask_utils", "geog_utils_to_add", "gtfs_utils", @@ -19,6 +19,5 @@ "schedule_rt_utils", "rt_dates", "rt_utils", - # "styleguide", - # "utils", + "utils_to_add", ] diff --git a/_shared_utils/shared_utils/utils_to_add.py b/_shared_utils/shared_utils/utils_to_add.py new file mode 100644 index 0000000000..dde5388726 --- /dev/null +++ b/_shared_utils/shared_utils/utils_to_add.py @@ -0,0 +1,54 @@ +import os +from pathlib import Path +from typing import Union + +import dask_geopandas as dg +import geopandas as gpd +from calitp_data_analysis import get_fs, utils + +fs = get_fs() + + +def parse_file_directory(file_name: str) -> str: + """ + Grab the directory of the filename. + For GCS bucket, we do not want '.' as the parent + directory, we want to parse and put together the + GCS filepath correctly. + """ + if str(Path(file_name).parent) != ".": + return str(Path(file_name).parent) + else: + return "" + + +def geoparquet_gcs_export(gdf: Union[gpd.GeoDataFrame, dg.GeoDataFrame], gcs_file_path: str, file_name: str, **kwargs): + """ + Save geodataframe as parquet locally, + then move to GCS bucket and delete local file. + + gdf: geopandas.GeoDataFrame + gcs_file_path: str + Ex: gs://calitp-analytics-data/data-analyses/my-folder/ + file_name: str + Filename, with or without .parquet. + """ + # Parse out file_name into stem (file_name_sanitized) + # and parent (file_directory_sanitized) + file_name_sanitized = Path(utils.sanitize_file_path(file_name)) + file_directory_sanitized = parse_file_directory(file_name) + + # Make sure GCS path includes the directory we want the file to go to + expanded_gcs = f"{Path(gcs_file_path).joinpath(file_directory_sanitized)}/" + expanded_gcs = str(expanded_gcs).replace("gs:/", "gs://") + + if isinstance(gdf, dg.GeoDataFrame): + gdf.to_parquet(f"{expanded_gcs}{file_name_sanitized}", overwrite=True, **kwargs) + + else: + gdf.to_parquet(f"{file_name_sanitized}.parquet", **kwargs) + fs.put( + f"{file_name_sanitized}.parquet", + f"{str(expanded_gcs)}{file_name_sanitized}.parquet", + ) + os.remove(f"{file_name_sanitized}.parquet", **kwargs) diff --git a/gtfs_funnel/config.yml b/gtfs_funnel/config.yml index a7e1d14020..ff71de2a41 100644 --- a/gtfs_funnel/config.yml +++ b/gtfs_funnel/config.yml @@ -1,9 +1,10 @@ raw_vp_file: "vp" usable_vp_file: "vp_usable" -vp_condensed_line_file: "vp_condensed" +vp_condensed_line_file: "condensed/vp_condensed" +vp_nearest_neighbor_file: "condensed/vp_nearest_neighbor" timestamp_col: "location_timestamp_local" time_min_cutoff: 10 stop_times_direction_file: "stop_times_direction" -trip_metrics_file: "schedule_trip_metrics" -route_direction_metrics_file: "schedule_route_direction_metrics" +trip_metrics_file: "schedule_trip/schedule_trip_metrics" +route_direction_metrics_file: "schedule_route_dir/schedule_route_direction_metrics" route_identification_file: "standardized_route_ids" \ No newline at end of file diff --git a/gtfs_funnel/route_typologies.py b/gtfs_funnel/route_typologies.py index d5af0744b1..d7765c0575 100644 --- a/gtfs_funnel/route_typologies.py +++ b/gtfs_funnel/route_typologies.py @@ -7,20 +7,27 @@ import pandas as pd from calitp_data_analysis.geography_utils import WGS84 -from calitp_data_analysis import utils +#from calitp_data_analysis import utils +from shared_utils import utils_to_add from segment_speed_utils import helpers, gtfs_schedule_wrangling from segment_speed_utils.project_vars import RT_SCHED_GCS, PROJECT_CRS catalog = intake.open_catalog( "../_shared_utils/shared_utils/shared_data_catalog.yml") -def assemble_scheduled_trip_metrics(analysis_date: str) -> pd.DataFrame: + +def assemble_scheduled_trip_metrics( + analysis_date: str, + dict_inputs: dict +) -> pd.DataFrame: """ Get GTFS schedule trip metrics including time-of-day buckets, scheduled service minutes, and median stop spacing. """ + STOP_TIMES_FILE = dict_inputs["stop_times_direction_file"] + df = gpd.read_parquet( - f"{RT_SCHED_GCS}stop_times_direction_{analysis_date}.parquet" + f"{RT_SCHED_GCS}{STOP_TIMES_FILE}_{analysis_date}.parquet" ) trips_to_route = helpers.import_scheduled_trips( @@ -173,7 +180,7 @@ def pop_density_by_shape(shape_df: gpd.GeoDataFrame): ROUTE_DIR_EXPORT = CONFIG_DICT["route_direction_metrics_file"] for date in analysis_date_list: - trip_metrics = assemble_scheduled_trip_metrics(date) + trip_metrics = assemble_scheduled_trip_metrics(date, CONFIG_DICT) trip_metrics.to_parquet( f"{RT_SCHED_GCS}{TRIP_EXPORT}_{date}.parquet") @@ -196,7 +203,7 @@ def pop_density_by_shape(shape_df: gpd.GeoDataFrame): how = "left" ) - utils.geoparquet_gcs_export( + utils_to_add.geoparquet_gcs_export( route_dir_metrics2, RT_SCHED_GCS, f"{ROUTE_DIR_EXPORT}_{date}" diff --git a/gtfs_funnel/vp_condenser.py b/gtfs_funnel/vp_condenser.py index 9b3a4892e7..b39d5f2992 100644 --- a/gtfs_funnel/vp_condenser.py +++ b/gtfs_funnel/vp_condenser.py @@ -11,7 +11,8 @@ from loguru import logger from calitp_data_analysis.geography_utils import WGS84 -from calitp_data_analysis import utils +#from calitp_data_analysis import utils +from shared_utils import utils_to_add from segment_speed_utils import vp_transform, wrangle_shapes from segment_speed_utils.project_vars import SEGMENT_GCS @@ -65,9 +66,9 @@ def condense_vp_to_linestring( align_dataframes = False ).compute().set_geometry("geometry").set_crs(WGS84) - utils.geoparquet_gcs_export( + utils_to_add.geoparquet_gcs_export( vp_condensed, - f"{SEGMENT_GCS}condensed/", + SEGMENT_GCS, f"{EXPORT_FILE}_{analysis_date}" ) @@ -76,7 +77,10 @@ def condense_vp_to_linestring( return -def prepare_vp_for_all_directions(analysis_date: str) -> gpd.GeoDataFrame: +def prepare_vp_for_all_directions( + analysis_date: str, + dict_inputs: dict +) -> gpd.GeoDataFrame: """ For each direction, exclude one the opposite direction and save out the arrays of valid indices. @@ -86,8 +90,11 @@ def prepare_vp_for_all_directions(analysis_date: str) -> gpd.GeoDataFrame: Subset vp_idx, location_timestamp_local and coordinate arrays to exclude southbound. """ + INPUT_FILE = dict_inputs["vp_condensed_line_file"] + EXPORT_FILE = dict_inputs["vp_nearest_neighbor_file"] + vp = delayed(gpd.read_parquet)( - f"{SEGMENT_GCS}condensed/vp_condensed_{analysis_date}.parquet", + f"{SEGMENT_GCS}{INPUT_FILE}_{analysis_date}.parquet", ) dfs = [ @@ -105,10 +112,10 @@ def prepare_vp_for_all_directions(analysis_date: str) -> gpd.GeoDataFrame: del results - utils.geoparquet_gcs_export( + utils_to_add.geoparquet_gcs_export( gdf, - f"{SEGMENT_GCS}condensed/", - f"vp_nearest_neighbor_{analysis_date}" + SEGMENT_GCS, + f"{EXPORT_FILE}_{analysis_date}" ) del gdf @@ -138,7 +145,7 @@ def prepare_vp_for_all_directions(analysis_date: str) -> gpd.GeoDataFrame: f"{time1 - start}" ) - prepare_vp_for_all_directions(analysis_date) + prepare_vp_for_all_directions(analysis_date, CONFIG_DICT) end = datetime.datetime.now() logger.info( diff --git a/rt_scheduled_v_ran/scripts/Makefile b/rt_scheduled_v_ran/scripts/Makefile index 42d37f3295..247da7f018 100644 --- a/rt_scheduled_v_ran/scripts/Makefile +++ b/rt_scheduled_v_ran/scripts/Makefile @@ -1,2 +1,2 @@ -spatial_accuracy: - python vp_spatial_accuracy.py +rt_sched_pipeline: + python rt_v_scheduled_trip.py diff --git a/rt_scheduled_v_ran/scripts/config.yml b/rt_scheduled_v_ran/scripts/config.yml new file mode 100644 index 0000000000..caa0b07af4 --- /dev/null +++ b/rt_scheduled_v_ran/scripts/config.yml @@ -0,0 +1,2 @@ +trip_metrics: "vp_trip/trip_metrics" +route_direction_metrics: "vp_route_dir/route_direction_metrics" \ No newline at end of file diff --git a/rt_scheduled_v_ran/scripts/route_aggregation.ipynb b/rt_scheduled_v_ran/scripts/route_aggregation.ipynb new file mode 100644 index 0000000000..8d38285955 --- /dev/null +++ b/rt_scheduled_v_ran/scripts/route_aggregation.ipynb @@ -0,0 +1,646 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "20e9270f-eef9-4e33-8e2b-260de6589a96", + "metadata": {}, + "source": [ + "## RT vs Schedule Route Aggregation Issues\n", + "\n", + "#### 1. Keep schedule and vp apart \n", + "For metrics derived from `vp_usable`, wherever we can merge with schedule info, do it. These metrics will include `vp_only` and `vp_and_schedule`, but **not** `schedule_only`. Schedule stuff `schedule_only` and `vp_and_schedule` will be done in `route_typologies`.\n", + "\n", + "Merge RT and schedule stuff **after** route-direction-time period aggregation.\n", + "Trips that are in vp will not have a route_id or direction_id, so our aggregation will wrap up all those into \"Unknown\" routes.\n", + "\n", + "For RT stuff, keep speed separate from other metrics for now.\n", + "\n", + "#### 2. Add columns for trip table \n", + "* Need `schedule_gtfs_dataset_key` from `vp_usable` and also `helpers.import_scheduled_trips` to get route-direction info.\n", + "* Add `time_of_day`, `peak_offpeak` column with `gtfs_schedule_wrangling`\n", + "\n", + "#### 3. Get metrics\n", + "Set up a function to do the division for certain percentages or other normalized metrics.\n", + "\n", + "This can be used for trip-level table, but will also need to be used after route-direction aggregation.\n", + "\n", + "#### 4. Set up for weighted metrics \n", + "Set up a function to help with weighted averages or percents. This should include all the columns we need to sum for a given grouping. \n", + "\n", + "For trips, this won't do anything, and it can be passed onto the metrics function in 3.\n", + "For route-direction-time_period, this will do something, and it will be passed onto the metrics function in 3.\n", + "\n", + "#### 5. Are functions generalizable?\n", + "For these functions for aggregation, put it separately in a script / `segment_speed_utils`. Leave this until it's obvious what can be used.\n", + "\n", + "#### 6. References to review while making changes\n", + "* how to set up speed-trip tables [add natural identifiers where necessary](https://github.com/cal-itp/data-analyses/blob/main/rt_segment_speeds/scripts/stop_arrivals_to_speed.py)\n", + "* [averaging of speeds](https://github.com/cal-itp/data-analyses/blob/main/rt_segment_speeds/scripts/average_speeds.py)\n", + "* crosswalk of operator identifiers [created here](https://github.com/cal-itp/data-analyses/blob/main/gtfs_funnel/crosswalk_gtfs_dataset_key_to_organization.py) and there is a [helper function](https://github.com/cal-itp/data-analyses/blob/main/rt_segment_speeds/segment_speed_utils/helpers.py#L169)...so use this! \n", + "* [segment_calcs](https://github.com/cal-itp/data-analyses/blob/main/rt_segment_speeds/segment_speed_utils/segment_calcs.py) for some aggregation\n", + "* [time helpers](https://github.com/cal-itp/data-analyses/blob/main/rt_segment_speeds/segment_speed_utils/time_helpers.py)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f8329ebd-74be-46d4-93bf-6031064b8ec5", + "metadata": {}, + "outputs": [], + "source": [ + "import dask.dataframe as dd\n", + "import pandas as pd\n", + "import yaml\n", + "\n", + "from shared_utils import rt_dates \n", + "from segment_speed_utils import helpers, gtfs_schedule_wrangling\n", + "from segment_speed_utils.project_vars import RT_SCHED_GCS, SEGMENT_GCS" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b7d1538a-9a40-40a4-bbe4-524e9c7b1477", + "metadata": {}, + "outputs": [], + "source": [ + "analysis_date = rt_dates.DATES[\"dec2023\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "41c09c7d-99f9-413d-86ba-73005d100e12", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'trip_metrics': 'vp_trip/trip_metrics',\n", + " 'route_direction_metrics': 'vp_route_dir/route_direction_metrics'}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with open(\"config.yml\") as f:\n", + " config_dict = yaml.safe_load(f)\n", + " \n", + "config_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "123a5f17-5e31-4fb4-a90a-3c1e1ebecdf2", + "metadata": {}, + "outputs": [], + "source": [ + "EXPORT_FILE = config_dict[\"trip_metrics\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b0395091-31ab-4b8c-b041-61328448c2be", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_parquet(\n", + " f\"{RT_SCHED_GCS}trip_level_metrics/{analysis_date}_metrics.parquet\"\n", + ")\n", + "df.to_parquet(f\"{RT_SCHED_GCS}{EXPORT_FILE}_{analysis_date}.parquet\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "1d9e1279-c066-4a41-aebe-3f6c2c477423", + "metadata": {}, + "outputs": [], + "source": [ + "gtfs_key = dd.read_parquet(\n", + " f\"{SEGMENT_GCS}vp_usable_{analysis_date}\",\n", + " columns = [\"schedule_gtfs_dataset_key\", \"trip_instance_key\"]\n", + ").drop_duplicates().compute()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0b543be9-1af5-4c3b-b00e-a0e279349c82", + "metadata": {}, + "outputs": [], + "source": [ + "df2 = pd.merge(\n", + " gtfs_key,\n", + " df,\n", + " on = \"trip_instance_key\",\n", + " how = \"inner\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a83d3abf-0f21-4b40-b67a-550077e42036", + "metadata": {}, + "outputs": [], + "source": [ + "trip_to_route = helpers.import_scheduled_trips(\n", + " analysis_date,\n", + " columns = [\"trip_instance_key\", \"route_id\", \"direction_id\"],\n", + " get_pandas = True\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cda3e9f8-f0e3-4b1b-977a-51f2af4f9eae", + "metadata": {}, + "outputs": [], + "source": [ + "# The left only merges are in vp, but not in schedule\n", + "# Fill in route_id and direction_id with missing\n", + "df3 = pd.merge(\n", + " df2,\n", + " trip_to_route,\n", + " on = \"trip_instance_key\",\n", + " how = \"left\",\n", + " indicator = \"sched_rt_category\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7958387d-6111-4bea-9e85-ea5236966144", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "both 77977\n", + "left_only 8151\n", + "right_only 0\n", + "Name: sched_rt_category, dtype: int64" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df3.sched_rt_category.value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "7c10477b-69aa-49bd-97ec-79ac3463fd19", + "metadata": {}, + "outputs": [], + "source": [ + "df3 = df3.assign(\n", + " route_id = df3.route_id.fillna(\"Unknown\"),\n", + " direction_id = df3.direction_id.astype(\"Int64\"),\n", + " sched_rt_category = df3.apply(\n", + " lambda x: \"vp_only\" if x.sched_rt_category==\"left_only\"\n", + " else \"vp_sched\",\n", + " axis=1)\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "792b172e-3027-416f-b702-9c9ff9d0c89e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "vp_sched 77977\n", + "vp_only 8151\n", + "Name: sched_rt_category, dtype: int64" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df3.sched_rt_category.value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "21df331d-397c-4247-ac2a-0a579df02698", + "metadata": {}, + "outputs": [], + "source": [ + "time_of_day = gtfs_schedule_wrangling.get_trip_time_buckets(\n", + " analysis_date\n", + ").pipe(\n", + " gtfs_schedule_wrangling.add_peak_offpeak_column\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "b7335363-a3be-456e-a3ba-f5d56265ba01", + "metadata": {}, + "outputs": [], + "source": [ + "df4 = pd.merge(\n", + " df3.drop(columns = \"service_minutes\"),\n", + " time_of_day,\n", + " on = \"trip_instance_key\",\n", + " how = \"inner\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "0dc44202-cb24-4829-8d8f-a26de4ef32a9", + "metadata": {}, + "source": [ + "Base off of this:\n", + "https://github.com/cal-itp/data-analyses/blob/main/rt_segment_speeds/segment_speed_utils/segment_calcs.py#L89-L132" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "088b4d11-d8c1-41c5-94c5-1d5f6fd9dee3", + "metadata": {}, + "outputs": [], + "source": [ + "def weighted_average_function(\n", + " df: pd.DataFrame, \n", + " group_cols: list\n", + "):\n", + " df2 = (df.groupby(group_cols + [\"peak_offpeak\"])\n", + " .agg({\n", + " \"trip_instance_key\": \"count\",\n", + " #\"rt_service_min\": \"mean\", # can't use this twice...\n", + " # only if we move this to portfolio_utils.aggregate()\n", + "\n", + " # weighted average for trip updates\n", + " \"total_min_w_gtfs\": \"sum\",\n", + " \"rt_service_min\": \"sum\",\n", + "\n", + " # weighted average of pings per min\n", + " \"total_pings_for_trip\": \"sum\",\n", + " \"service_minutes\": \"sum\", # is it this one or rt_service_min?\n", + "\n", + " # weighted spatial accuracy \n", + " \"total_vp\": \"sum\",\n", + " \"vp_in_shape\": \"sum\",\n", + " }).reset_index()\n", + " )\n", + "\n", + " return df2" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "81618bff-930e-4c5b-8147-7b8e65b403a8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + " | schedule_gtfs_dataset_key | \n", + "route_id | \n", + "direction_id | \n", + "sched_rt_category | \n", + "peak_offpeak | \n", + "trip_instance_key | \n", + "total_min_w_gtfs | \n", + "rt_service_min | \n", + "total_pings_for_trip | \n", + "service_minutes | \n", + "total_vp | \n", + "vp_in_shape | \n", + "
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | \n", + "015d67d5b75b5cf2b710bbadadfb75f5 | \n", + "17 | \n", + "0 | \n", + "vp_sched | \n", + "offpeak | \n", + "10 | \n", + "729 | \n", + "732.983333 | \n", + "2153 | \n", + "567.0 | \n", + "2153.0 | \n", + "1708.0 | \n", + "
1 | \n", + "015d67d5b75b5cf2b710bbadadfb75f5 | \n", + "17 | \n", + "0 | \n", + "vp_sched | \n", + "peak | \n", + "12 | \n", + "808 | \n", + "839.933333 | \n", + "2388 | \n", + "690.0 | \n", + "2388.0 | \n", + "2195.0 | \n", + "
2 | \n", + "015d67d5b75b5cf2b710bbadadfb75f5 | \n", + "17 | \n", + "1 | \n", + "vp_sched | \n", + "offpeak | \n", + "11 | \n", + "675 | \n", + "697.966667 | \n", + "1992 | \n", + "569.0 | \n", + "1992.0 | \n", + "1705.0 | \n", + "
3 | \n", + "015d67d5b75b5cf2b710bbadadfb75f5 | \n", + "17 | \n", + "1 | \n", + "vp_sched | \n", + "peak | \n", + "11 | \n", + "596 | \n", + "618.783333 | \n", + "1757 | \n", + "595.0 | \n", + "1757.0 | \n", + "1693.0 | \n", + "
4 | \n", + "015d67d5b75b5cf2b710bbadadfb75f5 | \n", + "219 | \n", + "0 | \n", + "vp_sched | \n", + "offpeak | \n", + "9 | \n", + "183 | \n", + "242.500000 | \n", + "533 | \n", + "152.0 | \n", + "533.0 | \n", + "428.0 | \n", + "
... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "... | \n", + "
5620 | \n", + "ff1bc5dde661d62c877165421e9ca257 | \n", + "ROUTEA | \n", + "0 | \n", + "vp_sched | \n", + "peak | \n", + "3 | \n", + "113 | \n", + "114.033333 | \n", + "340 | \n", + "90.0 | \n", + "340.0 | \n", + "155.0 | \n", + "
5621 | \n", + "ff1bc5dde661d62c877165421e9ca257 | \n", + "ROUTEA | \n", + "1 | \n", + "vp_sched | \n", + "offpeak | \n", + "8 | \n", + "409 | \n", + "408.433333 | \n", + "1252 | \n", + "222.0 | \n", + "1252.0 | \n", + "481.0 | \n", + "
5622 | \n", + "ff1bc5dde661d62c877165421e9ca257 | \n", + "ROUTEA | \n", + "1 | \n", + "vp_sched | \n", + "peak | \n", + "8 | \n", + "337 | \n", + "336.850000 | \n", + "1038 | \n", + "239.0 | \n", + "1038.0 | \n", + "522.0 | \n", + "
5623 | \n", + "ff1bc5dde661d62c877165421e9ca257 | \n", + "ROUTEB | \n", + "1 | \n", + "vp_sched | \n", + "offpeak | \n", + "3 | \n", + "159 | \n", + "159.416667 | \n", + "467 | \n", + "141.0 | \n", + "467.0 | \n", + "335.0 | \n", + "
5624 | \n", + "ff1bc5dde661d62c877165421e9ca257 | \n", + "ROUTEB | \n", + "1 | \n", + "vp_sched | \n", + "peak | \n", + "5 | \n", + "260 | \n", + "259.800000 | \n", + "773 | \n", + "235.0 | \n", + "773.0 | \n", + "601.0 | \n", + "
5625 rows × 12 columns
\n", + "