docs: def compute_route_stats_base(
      trip_stats_subset: DataFrame,
      headway_start_time: str = "07:00:00",
      headway_end_time: str = "19:00:00",
      *,
      split_directions: bool = False,
  ) -> DataFrame:
      """
      Compute stats for the given subset of trips stats.
  
      Parameters
      ----------
      trip_stats_subset : DataFrame
          Subset of the output of :func:`.trips.compute_trip_stats`
      split_directions : boolean
          If ``True``, then separate the stats by trip direction (0 or 1);
          otherwise aggregate trips visiting from both directions
      headway_start_time : string
          HH:MM:SS time string indicating the start time for computing
          headway stats
      headway_end_time : string
          HH:MM:SS time string indicating the end time for computing
          headway stats
  
      Returns
      -------
      DataFrame
          Columns are
  
          - ``'route_id'``
          - ``'route_short_name'``
          - ``'route_type'``
          - ``'direction_id'``
          - ``'num_trips'``: number of trips on the route in the subset
          - ``'num_trip_starts'``: number of trips on the route with
            nonnull start times
          - ``'num_trip_ends'``: number of trips on the route with nonnull
            end times that end before 23:59:59
          - ``'is_loop'``: 1 if at least one of the trips on the route has
            its ``is_loop`` field equal to 1; 0 otherwise
          - ``'is_bidirectional'``: 1 if the route has trips in both
            directions; 0 otherwise
          - ``'start_time'``: start time of the earliest trip on the route
          - ``'end_time'``: end time of latest trip on the route
          - ``'max_headway'``: maximum of the durations (in minutes)
            between trip starts on the route between
            ``headway_start_time`` and ``headway_end_time`` on the given
            dates
          - ``'min_headway'``: minimum of the durations (in minutes)
            mentioned above
          - ``'mean_headway'``: mean of the durations (in minutes)
            mentioned above
          - ``'peak_num_trips'``: maximum number of simultaneous trips in
            service (for the given direction, or for both directions when
            ``split_directions==False``)
          - ``'peak_start_time'``: start time of first longest period
            during which the peak number of trips occurs
          - ``'peak_end_time'``: end time of first longest period during
            which the peak number of trips occurs
          - ``'service_duration'``: total of the duration of each trip on
            the route in the given subset of trips; measured in hours
          - ``'service_distance'``: total of the distance traveled by each
            trip on the route in the given subset of trips; measured in
            whatever distance units are present in ``trip_stats_subset``;
            contains all ``np.nan`` entries if ``feed.shapes is None``
          - ``'service_speed'``: service_distance/service_duration;
            measured in distance units per hour
          - ``'mean_trip_distance'``: service_distance/num_trips
          - ``'mean_trip_duration'``: service_duration/num_trips
  
      If not ``split_directions``, then remove the
      direction_id column and compute each route's stats,
      except for headways, using
      its trips running in both directions.
      In this case, (1) compute max headway by taking the max of the
      max headways in both directions; (2) compute mean headway by
      taking the weighted mean of the mean headways in both
      directions.
  
      If ``trip_stats_subset`` is empty, return an empty DataFrame.
  
      Raise a ValueError if ``split_directions`` and no non-NaN
      direction ID values present
      """
      if trip_stats_subset.empty:
          return pd.DataFrame()
  
      # Convert trip start and end times to seconds to ease calculations below
      f = trip_stats_subset.copy()
      f[["start_time", "end_time"]] = f[["start_time", "end_time"]].applymap(
          hp.timestr_to_seconds
      )
  
      headway_start = hp.timestr_to_seconds(headway_start_time)
      headway_end = hp.timestr_to_seconds(headway_end_time)
  
      def compute_route_stats_split_directions(group):
          # Take this group of all trips stats for a single route
          # and compute route-level stats.
          d = OrderedDict()
          d["route_short_name"] = group["route_short_name"].iat[0]
          d["route_type"] = group["route_type"].iat[0]
          d["num_trips"] = group.shape[0]
          d["num_trip_starts"] = group["start_time"].count()
          d["num_trip_ends"] = group.loc[
              group["end_time"] < 24 * 3600, "end_time"
          ].count()
          d["is_loop"] = int(group["is_loop"].any())
          d["start_time"] = group["start_time"].min()
          d["end_time"] = group["end_time"].max()
  
          # Compute max and mean headway
          stimes = group["start_time"].values
          stimes = sorted(
              [
                  stime
                  for stime in stimes
                  if headway_start <= stime <= headway_end
              ]
          )
          headways = np.diff(stimes)
          if headways.size:
              d["max_headway"] = np.max(headways) / 60  # minutes
              d["min_headway"] = np.min(headways) / 60  # minutes
              d["mean_headway"] = np.mean(headways) / 60  # minutes
          else:
              d["max_headway"] = np.nan
              d["min_headway"] = np.nan
              d["mean_headway"] = np.nan
  
          # Compute peak num trips
          active_trips = hp.get_active_trips_df(
              group[["start_time", "end_time"]]
          )
          times, counts = active_trips.index.values, active_trips.values
          start, end = hp.get_peak_indices(times, counts)
          d["peak_num_trips"] = counts[start]
          d["peak_start_time"] = times[start]
          d["peak_end_time"] = times[end]
  
          d["service_distance"] = group["distance"].sum()
          d["service_duration"] = group["duration"].sum()
  
          return pd.Series(d)
  
      def compute_route_stats(group):
          d = OrderedDict()
          d["route_short_name"] = group["route_short_name"].iat[0]
          d["route_type"] = group["route_type"].iat[0]
          d["num_trips"] = group.shape[0]
          d["num_trip_starts"] = group["start_time"].count()
          d["num_trip_ends"] = group.loc[
              group["end_time"] < 24 * 3600, "end_time"
          ].count()
          d["is_loop"] = int(group["is_loop"].any())
          d["is_bidirectional"] = int(group["direction_id"].unique().size > 1)
          d["start_time"] = group["start_time"].min()
          d["end_time"] = group["end_time"].max()
  
          # Compute headway stats
          headways = np.array([])
          for direction in [0, 1]:
              stimes = group[group["direction_id"] == direction][
                  "start_time"
              ].values
              stimes = sorted(
                  [
                      stime
                      for stime in stimes
                      if headway_start <= stime <= headway_end
                  ]
              )
              headways = np.concatenate([headways, np.diff(stimes)])
          if headways.size:
              d["max_headway"] = np.max(headways) / 60  # minutes
              d["min_headway"] = np.min(headways) / 60  # minutes
              d["mean_headway"] = np.mean(headways) / 60  # minutes
          else:
              d["max_headway"] = np.nan
              d["min_headway"] = np.nan
              d["mean_headway"] = np.nan
  
          # Compute peak num trips
          active_trips = hp.get_active_trips_df(
              group[["start_time", "end_time"]]
          )
          times, counts = active_trips.index.values, active_trips.values
          start, end = hp.get_peak_indices(times, counts)
          d["peak_num_trips"] = counts[start]
          d["peak_start_time"] = times[start]
          d["peak_end_time"] = times[end]
  
          d["service_distance"] = group["distance"].sum()
          d["service_duration"] = group["duration"].sum()
  
          return pd.Series(d)
  
      if split_directions:
          f = f.loc[lambda x: x.direction_id.notnull()].assign(
              direction_id=lambda x: x.direction_id.astype(int)
          )
          if f.empty:
              raise ValueError(
                  "At least one trip stats direction ID value "
                  "must be non-NaN."
              )
  
          g = (
              f.groupby(["route_id", "direction_id"])
              .apply(compute_route_stats_split_directions)
              .reset_index()
          )
  
          # Add the is_bidirectional column
          def is_bidirectional(group):
              d = {}
              d["is_bidirectional"] = int(
                  group["direction_id"].unique().size > 1
              )
              return pd.Series(d)
  
          gg = g.groupby("route_id").apply(is_bidirectional).reset_index()
          g = g.merge(gg)
      else:
          g = f.groupby("route_id").apply(compute_route_stats).reset_index()
  
      # Compute a few more stats
      g["service_speed"] = g["service_distance"] / g["service_duration"]
      g["mean_trip_distance"] = g["service_distance"] / g["num_trips"]
      g["mean_trip_duration"] = g["service_duration"] / g["num_trips"]
  
      # Convert route times to time strings
      g[["start_time", "end_time", "peak_start_time", "peak_end_time"]] = g[
          ["start_time", "end_time", "peak_start_time", "peak_end_time"]
      ].applymap(lambda x: hp.timestr_to_seconds(x, inverse=True))
  
      return g