Skip to content

Instantly share code, notes, and snippets.

@alpha-beta-soup
Last active March 22, 2018 20:51
Show Gist options
  • Save alpha-beta-soup/2d3acc4558350318155c to your computer and use it in GitHub Desktop.
Save alpha-beta-soup/2d3acc4558350318155c to your computer and use it in GitHub Desktop.
A Python function to calculate the headways of transit vehicles arranged sequentially for a week, accounting for time over midnight over one or more days. Uses NumPy.
import numpy as np
def headway_array(arrivals_array, maxseconds=24*60*60, daysinweek=7):
'''
Given a numpy.array object with...
> the number of rows equal to the number of days in a week, <daysinweek>
> the number of rows equal to the number of trips made on a public transport
route
> the array values equal to the time at which the vehicle arrives at the
stop, expressed in seconds since midnight for each day. None values can be
used as placeholders for vehicles that arrive at some time on some days
of the week, and not at all on other days.
...this function returns an array of the same shape, but with values
transformed into the time (in seconds) until the next vehicle arrives.
This accounts for time over one or more midnight periods (so the headway of
the last vehicle on a Friday night on a route that doesn't run on weekends
is the number of seconds until the first service on Monday morning).
NOTE: There are possibly applications outside of transport where this operation
could be useful, but I'm not interested in those, so my terminology
(and varible names) are domain specific. Use your imagination to rename
the variables if you desire.
NOTE: If two vehicles arrive at the exact same time, the headway is not 0.
Rather, the arrival of two vehicles at the same time is equivalent
to there only being one arrival at that time, and the headway is the
time until the next arrival at a time beyond the arrival of those
vehicles.
NOTE: The result, by definition, will be an array whose numerical values
sum to the total number of time instances possible, <maxseconds> *
<daysinweek>.
MATHS: I don't know if there's a name for this type of matrix manipulation,
but in order to calculate the number I am after, I use the following formula
that I developed:
s1 : seconds since midnight the trip arrives (whatever day)
s2 : seconds since midnight the NEXT trip arrives (whatever day)
maxS : maximum number of time moments in one whole period (seconds=24*60*60)
changeD : the number of midnights that elapse between s1 and s2 (i.e.,
the number of columns they are apart; same column = 0).
The headway, h, between any two arrival times in the matrix can be found by
calculating:
h = s2 + (maxS - s1) + (changeD - 1) * maxS
I really doubt that I'm the first to think of it... but pretty nifty! It works
whether changeD is 0 or > 0.
I force it to max out at <maxseconds> * <daysinweek> so nothing untoward happens.
EXAMPLES:
>>> a = np.array([[None,1,1,1,1,None,None],
[None,2,2,2,2,None,2],
[3,3,3,3,3,3,None],
[4,4,4,4,4,4,None],
[10,5,5,5,None,5,None]])
>>> b = np.array([[1,None,None,None,None,None,None],
[None,None,None,None,None,None,None],
[3,None,None,None,None,None,None],
[None,None,None,None,None,None,None],
[None,None,None,None,None,None,None]])
>>> headway_array(arrivals_array=a, maxseconds=10)
[[None 1 1 1 1 None None]
[None 1 1 1 1 None 11]
[1 1 1 1 1 1 None]
[6 1 1 1 9 1 None]
[1 6 6 6 None 7 None]]
>>> headway_array(arrivals_array=b, maxseconds=10)
[[2 None None None None None None]
[None None None None None None None]
[68 None None None None None None]
[None None None None None None None]
[None None None None None None None]]
'''
def solve(s1, s2, changeD):
maximum = maxseconds * daysinweek
return min(maximum, s2 + (maxseconds - s1) + (changeD - 1) * maxseconds)
numtrips = len(arrivals_array)
arrayexists = False
for tripindex, trip in enumerate(arrivals_array):
trip_headway = []
nextripindex = tripindex + 1
if nextripindex > numtrips: nextripindex = 0
for dow, arrival in enumerate(trip):
originaldow = dow # Keep note of the weekday of the considered trip
if arrival is not None:
try:
# Get time until next equivalent trip on the same day
# secondstillnext = arrivals_array[nextripindex][dow] - arrival
secondstillnext = solve(arrival, arrivals_array[nextripindex][dow], 0)
if secondstillnext <= 0:
# If the next trip is at the same time,
# or you have data issues and it's _before_ the current trip
raise Exception
except:
# Except the next row is a Nonetype (deliberate),
# or there are two vehicles at the same time (deliberate),
# or your data is sorted improperly (SILENT ERROR).
unsuccessful = True # Have not found a valid trip yet
DAYCHANGES = 0 # Going to be counting this
interior_tripindex = nextripindex + 1 # The unsuccesful row
while unsuccessful:
if interior_tripindex >= (numtrips):
# If it has looked down to the last row without success,
dow = dow + 1 # advance the DOW
if dow > 6: dow = 0 # (and maybe reset the DOW)
DAYCHANGES += 1 # add to DAYCHANGES
interior_tripindex = 0 # reset the interior_tripindex to 0
try:
secondstillnext = solve(arrival, arrivals_array[interior_tripindex][dow], DAYCHANGES)
if originaldow == dow and interior_tripindex == tripindex:
# Then the next trip is the same trip, 7 days later
secondstillnext = maxseconds * 7
if secondstillnext > 0:
unsuccessful = False # Found a valid trip
else:
unsuccessful = True
raise Exception
except:
interior_tripindex += 1 # Next row's index
unsuccessful = True # Keep looking!
else:
# None is a placeholder
secondstillnext = None
# Append result to a list that will be put into result array
trip_headway.append(secondstillnext)
if not arrayexists:
# If the array hasn't been declared,
# write a new array and inser first row (trip arrival over the week)
retarray = np.array([trip_headway])
arrayexists = True
else:
# For existigng arrays, insert additional rows (trips over the week)
retarray = np.append(retarray, [trip_headway], 0)
# Returned object is a np.array in the same shape as the input array
return retarray
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment