docs: def compute_feed_stats(
      feed: "Feed", trip_stats: DataFrame, dates: List[str]
  ) -> DataFrame:
      """
      Compute some feed stats for the given dates and trip stats.
  
      Parameters
      ----------
      feed : Feed
      trip_stats : DataFrame
          Trip stats to consider in the format output by
          :func:`.trips.compute_trip_stats`
      dates : string or list
          A YYYYMMDD date string or list thereof indicating the date(s)
          for which to compute stats
  
      Returns
      -------
      DataFrame
          The columns are
  
          - ``'date'``
          - ``'num_stops'``: number of stops active on the date
          - ``'num_routes'``: number of routes active on the date
          - ``'num_trips'``: number of trips that start on the date
          - ``'num_trip_starts'``: number of trips with nonnull start
            times on the date
          - ``'num_trip_ends'``: number of trips with nonnull start times
            and nonnull end times on the date, ignoring trips that end
            after 23:59:59 on the date
          - ``'peak_num_trips'``: maximum number of simultaneous trips in
            service on the date
          - ``'peak_start_time'``: start time of first longest period
            during which the peak number of trips occurs on the date
          - ``'peak_end_time'``: end time of first longest period during
            which the peak number of trips occurs on the date
          - ``'service_distance'``: sum of the service distances for the
            active routes on the date
          - ``'service_duration'``: sum of the service durations for the
            active routes on the date
          - ``'service_speed'``: service_distance/service_duration on the
            date
  
          Dates with no trip activity will have null stats.
          Exclude dates that lie outside of the Feed's date range.
          If all the dates given lie outside of the Feed's date range,
          then return an empty DataFrame.
  
      Notes
      -----
      - The route and trip stats for date d contain stats for trips that
        start on date d only and ignore trips that start on date d-1 and
        end on date d
      - Assume the following feed attributes are not ``None``:
  
          * Those used in :func:`.trips.get_trips`
          * Those used in :func:`.routes.get_routes`
          * Those used in :func:`.stops.get_stops`
  
      """
      dates = feed.restrict_dates(dates)
      if not dates:
          return pd.DataFrame()
  
      ts = trip_stats.copy()
      activity = feed.compute_trip_activity(dates)
      stop_times = feed.stop_times.copy()
  
      # Convert timestrings to seconds for quicker calculations
      ts[["start_time", "end_time"]] = ts[["start_time", "end_time"]].applymap(
          hp.timestr_to_seconds
      )
  
      # Collect stats for each date, memoizing stats by trip ID sequence
      # to avoid unnecessary recomputations.
      # Store in dictionary of the form
      # trip ID sequence ->
      # [stats dictionary, date list that stats apply]
      stats_and_dates_by_ids = {}
      cols = [
          "num_stops",
          "num_routes",
          "num_trips",
          "num_trip_starts",
          "num_trip_ends",
          "peak_num_trips",
          "peak_start_time",
          "peak_end_time",
          "service_distance",
          "service_duration",
          "service_speed",
      ]
      null_stats = {c: np.nan for c in cols}
      for date in dates:
          stats = {}
          ids = tuple(activity.loc[activity[date] > 0, "trip_id"])
          if ids in stats_and_dates_by_ids:
              # Append date to date list
              stats_and_dates_by_ids[ids][1].append(date)
          elif not ids:
              # Null stats
              stats_and_dates_by_ids[ids] = [null_stats, [date]]
          else:
              # Compute stats
              f = ts[ts["trip_id"].isin(ids)].copy()
              stats["num_stops"] = stop_times.loc[
                  stop_times["trip_id"].isin(ids), "stop_id"
              ].nunique()
              stats["num_routes"] = f["route_id"].nunique()
              stats["num_trips"] = f.shape[0]
              stats["num_trip_starts"] = f["start_time"].count()
              stats["num_trip_ends"] = f.loc[
                  f["end_time"] < 24 * 3600, "end_time"
              ].count()
              stats["service_distance"] = f["distance"].sum()
              stats["service_duration"] = f["duration"].sum()
              stats["service_speed"] = (
                  stats["service_distance"] / stats["service_duration"]
              )
  
              # Compute peak stats, which is the slowest part
              active_trips = hp.get_active_trips_df(
                  f[["start_time", "end_time"]]
              )
              times, counts = active_trips.index.values, active_trips.values
              start, end = hp.get_peak_indices(times, counts)
              stats["peak_num_trips"] = counts[start]
              stats["peak_start_time"] = times[start]
              stats["peak_end_time"] = times[end]
  
              # Record stats
              stats_and_dates_by_ids[ids] = [stats, [date]]
  
      # Assemble stats into DataFrame
      rows = []
      for stats, dates_ in stats_and_dates_by_ids.values():
          for date in dates_:
              s = copy.copy(stats)
              s["date"] = date
              rows.append(s)
      f = pd.DataFrame(rows).sort_values("date")
  
      # Convert seconds back to timestrings
      times = ["peak_start_time", "peak_end_time"]
      f[times] = f[times].applymap(
          lambda t: hp.timestr_to_seconds(t, inverse=True)
      )
  
      return f