Skip to content

frequencies_to_stop_times slow execution speed in large datasets #89

Description

@cseveren

Thanks for the awesome and helpful package! I get a lot of value out of frequencies_to_stop_times, but I've noticed that it's quite slow when working at scale (i.e., it takes several hours on my reasonably powerful desktop to go from 37k stop times and 1k trips to 4.4mil stop times and 140k trips for recent Mexico City freq-based GTFS feeds). This is almost assuredly due to the use of rbind here to append row by row onto the eventually quite huge stop_times data.table which is used to store and eventually output results:

 stop_times <- rbind (stop_times, stop_times_trip_i)

I propose three alternatives, which I've tried to implement locally on my machine but I've not worked with rcpp before and so I couldn't make work. I can't quite tell if stop_times_trip_i is always 1 row long or not. If so:

  • Preallocate stop_times with some upper bound number of rows (perhaps user set), and then simply replace the above with:
     stop_times[i, ] <- stop_times_trip_i
    

Else (if it's number of rows vary), here are two other ideas:

  • Preallocate stop_times with some upper bound number of rows (perhaps user set), and replace a group of rows, as above
  • Accumulate results in smaller data.tables and then rbind, there's an opportunity for a couple of levels of recursion. Specifically, the for trips block could be (sorry for the stupid names):
    for (trip in trips)  {

        # n is to  be added to trip_id
        n <- 1

        # order frequencies table by trip_id and start_time
        frequencies_trip <-
            gtfs_cp$frequencies [order (trip_id, start_time)] [trip_id == trip]

        # in case end_time of the previous period and start_time of the next are
        # equal:
        frequencies_trip [end_time ==
                          data.table::shift (start_time, 1,
                                             type = "lead"),
                            end_time := end_time - 1]

        stop_times_trip <-
            gtfs_cp$stop_times [order (stop_sequence)] [trip_id == trip]

        # in case of the first arrival time > 0, then reset them:
        if (stop_times_trip [1] [["arrival_time"]] > 0)  {
            stop_times_trip [, c ("arrival_time", "departure_time") :=
                             list(
                arrival_time - stop_times_trip [1] [["arrival_time"]],
                departure_time - stop_times_trip [1] [["arrival_time"]]
                )]
        }

        start_t <- min (frequencies_trip$start_time)

        headway <- headway_old <- frequencies_trip [1] [["headway_secs"]]

        ## NEW LINE ##
        stop_times_small <- stop_times

        for (i in row (frequencies_trip))  {

            end_t <- frequencies_trip [i] [["end_time"]]
            headway <- frequencies_trip [i] [["headway_secs"]]

            # in order to ensure a 'smooth' transition between frequency periods
            ifelse (
                headway_old - start_t +
                    frequencies_trip [i] [["start_time"]] < headway,
                start_t <- start_t - headway_old + headway,
                start_t <- frequencies_trip [i] [["start_time"]]
            )

            ## NEW LINE ##
            stop_times_small_small <- stop_times

            # multiply stop_times for all trips based on a given frequency
            while (start_t < end_t)  {

                stop_times_trip_i <-
                    data.table::copy (stop_times_trip) [, c ("arrival_time",
                                                             "departure_time",
                                                             "trip_id_f") :=
                            list ((arrival_time + start_t),
                                  (departure_time + start_t),
                                  paste (trip_id, n, sep = "_"))]

                n <- n + 1

                # EDITED BELOW #
                stop_times_small_small <- rbind (stop_times_small_small, stop_times_trip_i)

                start_t <- start_t + headway
            }
            # EDITED BELOW #
            stop_times_small <- rbind (stop_times_small, stop_times_small_small)

            headway_old <- headway

        }
        # EDITED BELOW #
        stop_times <- rbind (stop_times, stop_times_small_small)

    }

This last option, while not totally efficient, will probably substantially increase performance without needing to explicitly deal with preallocation. Again, thanks for the great package!

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions