Last active
March 22, 2018 20:51
-
-
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.
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
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