P4Learned Delivery Routing
3

Code and data line · 03 of 13

Data dictionary

Real trips and generated orders never mix. This is every column on both sides, the filter funnel from 2.46 million trips to 300,000, and how the generated parts are seeded.

Two kinds of data: real (NYC TLC yellow-taxi trips, January 2022) and generated (orders, windows, demands, service times, vans; seeded). Generated data never touches the trip files; real data never contains any order information.

1. Raw source files (real, data/raw/, gitignored)

File Source Size
yellow_tripdata_2022-01.parquet https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2022-01.parquet 38.1 MB, 2,463,931 rows
taxi_zone_lookup.csv https://d37ci6vzurychx.cloudfront.net/misc/taxi_zone_lookup.csv 265 zones
taxi_zones.zip https://d37ci6vzurychx.cloudfront.net/misc/taxi_zones.zip shapefile, EPSG:2263 (feet)

Columns read from the parquet (the script fails loudly if any is missing; all five exist in 2022-01): tpep_pickup_datetime, tpep_dropoff_datetime, PULocationID, DOLocationID, trip_distance.

2. Filtering (make_data.py -> delivery/data.py::preprocess)

Step Rows left
raw 2,463,931
pickup inside January 2022 (drops mis-dated records) 2,463,879
weekdays only (Mon-Fri) 1,754,221
duration 1-120 min 1,731,617
distance > 0 and speed <= 60 mph (outliers) 1,722,324
both ends in the 20 busiest zones (pickups + drop-offs; unknown zones 264/265 excluded) 670,489
seeded uniform sample (seed 42) 300,000

Split by day: train Jan 3-19 (173,350), validation Jan 20-21 (33,058, early stopping only), test Jan 24-31 (93,592). Jan 1-2, 8-9, ... are weekends and are removed. Jan 17 (MLK Day) is kept.

The 20 zones (index 0-19): Clinton East, East Chelsea, Gramercy, JFK Airport, Lenox Hill East, Lenox Hill West, Lincoln Square East, Midtown Center, Midtown East, Midtown North, Murray Hill, Penn Station/Madison Sq West, Sutton Place/Turtle Bay North, Times Sq/Theatre District, Union Sq, Upper East Side North, Upper East Side South, Upper West Side North, Upper West Side South, Yorkville West. 19 are in Manhattan; JFK Airport is the only far zone.

3. Processed trips (real, data/processed/trips.parquet; sample in tests/fixtures/sample_trips.csv)

Column Type Meaning
pickup datetime pickup timestamp (not in the fixture)
date str pickup date YYYY-MM-DD (used for the day split)
pu_id, do_id int original TLC LocationID of pickup / drop-off
pu, do int 0-19 zone index (sorted LocationID order)
hour int 0-23 pickup hour
weekday int 0-4 Mon=0 ... Fri=4
distance_mi float reported trip distance (miles); used ONLY to build the pair mean distance
duration_min float target: drop-off minus pickup, minutes, in [1, 120]
split str train / val / test

4. Zones and pair distance (real-derived)

zones.csv: idx, LocationID, borough, zone, service_zone, cx_km, cy_km (area-weighted polygon centroid from the shapefile, km in NY State Plane), n_pickups, n_dropoffs (in the 300k sample).

pair_distance.npy (20x20, miles): mean distance_mi per (pu, do) over the train split only. All 400 pairs were observed in train; the fallback (linear fit on centroid distance: -0.176 + 1.428 x centroid miles) exists for other months but was not needed. Sparsity: 8,406 of 9,600 (pu, do, hour) cells appear in train, 2,026 of them with < 5 trips (mostly night hours).

5. NN inputs (derived)

pickup zone one-hot (20), drop-off zone one-hot (20), hour one-hot (24) + sin/cos(hour), weekday one-hot (5), pair mean distance standardized + log1p standardized. 73 features. The actual trip distance is never an input (it is unknown when planning).

6. Predicted time matrices (models/time_matrices.npz)

Keys nn, hist, dist, each shaped (5 weekdays, 24 hours, 20, 20): predicted taxi minutes from zone i to zone j departing in that hour. CSV of the NN matrix for Tuesday: outputs/time_matrix_nn_tuesday.csv (hour, pu_idx, do_idx, pu_zone, do_zone, minutes). Van time = taxi time x 1.2 (documented assumption VAN_FACTOR; vans are slower, park, etc.).

7. Generated orders and vans (delivery/instances.py, instances/*.json)

Generator generate_instance(n_orders, seed, ...) with numpy.random.default_rng(seed):

Field How it is generated
depot zone index; random unless given (demo uses 7 = Midtown Center)
weekday uniform 0-4 (selects which weekday's matrix is used)
order id 0..n-1
order zone drawn from the 20 zones with probability proportional to real drop-off counts
earliest uniform in [09:00, 17:00 - width], rounded to 15 min (minutes after midnight)
latest earliest + width, width uniform from {30, 45, 60, 90, 120} (standard) or a narrower set (tight/rush scenarios: {15, 30} or {15, 30, 45}; demo {15, 30, 45, 60})
demand 1, 2 or 3 units with probability 0.5 / 0.3 / 0.2
service 5, 7 or 10 minutes (service-time variation extension)
van capacity ceil(total demand x cap_slack / 3), cap_slack 1.25 by default, 1.0 in the CSP experiment
van shift_start/shift_end 08:00-17:00, 09:00-18:00, 10:00-19:00 (van 0, 1, 2)
van max_stops 10 (spec: up to 10 stops per van); experiment 2 uses min(10, ceil(n/3)+1)
return_to_depot true: routes end back at the depot (return-trip extension)

generate_route_instance(n_stops, seed, tm) (experiment 1, one van): builds a hidden random tour with the time model and puts each window (60/90/120 min) around the hidden arrival time, so the instance is feasible by construction; ids are shuffled afterwards.

The instance set: instances/n{15,20,30}_s{0..9}.json (+ manifest.json).

Download the Markdown source