nodes: 0, module; 1, function_definition; 2, function_name:build_route_timetable; 3, parameters; 4, type; 5, block; 6, typed_parameter; 7, typed_parameter; 8, typed_parameter; 9, identifier:DataFrame; 10, expression_statement; 11, expression_statement; 12, if_statement; 13, expression_statement; 14, expression_statement; 15, expression_statement; 16, expression_statement; 17, for_statement; 18, expression_statement; 19, return_statement; 20, identifier:feed; 21, type; 22, identifier:route_id; 23, type; 24, identifier:dates; 25, type; 26, comment:"""
      Return a timetable for the given route and dates.
  
      Parameters
      ----------
      feed : Feed
      route_id : string
          ID of a route in ``feed.routes``
      dates : string or list
          A YYYYMMDD date string or list thereof
  
      Returns
      -------
      DataFrame
          The columns are all those in ``feed.trips`` plus those in
          ``feed.stop_times`` plus ``'date'``, and the trip IDs
          are restricted to the given route ID.
          The result is sorted first by date and then by grouping by
          trip ID and sorting the groups by their first departure time.
  
          Skip dates outside of the Feed's dates.
  
          If there is no route activity on the given dates, then return
          an empty DataFrame.
  
      Notes
      -----
      Assume the following feed attributes are not ``None``:
  
      - ``feed.stop_times``
      - Those used in :func:`.trips.get_trips`
  
      """; 27, assignment; 28, not_operator; 29, block; 30, assignment; 31, assignment; 32, assignment; 33, assignment; 34, identifier:date; 35, identifier:dates; 36, comment:# Slice to trips active on date; 37, block; 38, assignment; 39, call; 40, string:"Feed"; 41, identifier:str; 42, generic_type; 43, identifier:dates; 44, call; 45, identifier:dates; 46, return_statement; 47, identifier:t; 48, call; 49, identifier:t; 50, call; 51, identifier:a; 52, call; 53, identifier:frames; 54, list; 55, expression_statement; 56, expression_statement; 57, expression_statement; 58, comment:# Groupby trip ID and sort groups by their minimum departure time.; 59, comment:# For some reason NaN departure times mess up the transform below.; 60, comment:# So temporarily fill NaN departure times as a workaround.; 61, expression_statement; 62, expression_statement; 63, expression_statement; 64, identifier:f; 65, call; 66, attribute; 67, argument_list; 68, identifier:List; 69, type_parameter; 70, attribute; 71, argument_list; 72, call; 73, attribute; 74, argument_list; 75, attribute; 76, argument_list; 77, attribute; 78, argument_list; 79, assignment; 80, assignment; 81, assignment; 82, assignment; 83, assignment; 84, call; 85, attribute; 86, argument_list; 87, call; 88, identifier:drop; 89, list; 90, keyword_argument; 91, type; 92, identifier:feed; 93, identifier:restrict_dates; 94, identifier:dates; 95, attribute; 96, argument_list; 97, identifier:pd; 98, identifier:merge; 99, attribute; 100, attribute; 101, subscript; 102, identifier:copy; 103, identifier:feed; 104, identifier:compute_trip_activity; 105, identifier:dates; 106, identifier:ids; 107, subscript; 108, identifier:f; 109, call; 110, subscript; 111, identifier:date; 112, subscript; 113, call; 114, subscript; 115, call; 116, attribute; 117, argument_list; 118, identifier:pd; 119, identifier:concat; 120, identifier:frames; 121, attribute; 122, argument_list; 123, string:"min_dt"; 124, string:"dt"; 125, identifier:axis; 126, integer:1; 127, identifier:str; 128, identifier:pd; 129, identifier:DataFrame; 130, identifier:feed; 131, identifier:trips; 132, identifier:feed; 133, identifier:stop_times; 134, identifier:t; 135, comparison_operator:t["route_id"] == route_id; 136, attribute; 137, comparison_operator:a[date] == 1; 138, string:"trip_id"; 139, attribute; 140, argument_list; 141, identifier:f; 142, string:"date"; 143, identifier:f; 144, string:"dt"; 145, attribute; 146, argument_list; 147, identifier:f; 148, string:"min_dt"; 149, attribute; 150, argument_list; 151, identifier:frames; 152, identifier:append; 153, identifier:f; 154, identifier:f; 155, identifier:sort_values; 156, list; 157, subscript; 158, identifier:route_id; 159, identifier:a; 160, identifier:loc; 161, subscript; 162, integer:1; 163, subscript; 164, identifier:copy; 165, subscript; 166, identifier:fillna; 167, keyword_argument; 168, subscript; 169, identifier:transform; 170, identifier:min; 171, string:"date"; 172, string:"min_dt"; 173, string:"stop_sequence"; 174, identifier:t; 175, string:"route_id"; 176, identifier:a; 177, identifier:date; 178, identifier:t; 179, call; 180, identifier:f; 181, string:"departure_time"; 182, identifier:method; 183, string:"ffill"; 184, call; 185, string:"dt"; 186, attribute; 187, argument_list; 188, attribute; 189, argument_list; 190, subscript; 191, identifier:isin; 192, identifier:ids; 193, identifier:f; 194, identifier:groupby; 195, string:"trip_id"; 196, identifier:t; 197, string:"trip_id"
code: def build_route_timetable(
      feed: "Feed", route_id: str, dates: List[str]
  ) -> DataFrame:
      """
      Return a timetable for the given route and dates.
  
      Parameters
      ----------
      feed : Feed
      route_id : string
          ID of a route in ``feed.routes``
      dates : string or list
          A YYYYMMDD date string or list thereof
  
      Returns
      -------
      DataFrame
          The columns are all those in ``feed.trips`` plus those in
          ``feed.stop_times`` plus ``'date'``, and the trip IDs
          are restricted to the given route ID.
          The result is sorted first by date and then by grouping by
          trip ID and sorting the groups by their first departure time.
  
          Skip dates outside of the Feed's dates.
  
          If there is no route activity on the given dates, then return
          an empty DataFrame.
  
      Notes
      -----
      Assume the following feed attributes are not ``None``:
  
      - ``feed.stop_times``
      - Those used in :func:`.trips.get_trips`
  
      """
      dates = feed.restrict_dates(dates)
      if not dates:
          return pd.DataFrame()
  
      t = pd.merge(feed.trips, feed.stop_times)
      t = t[t["route_id"] == route_id].copy()
      a = feed.compute_trip_activity(dates)
  
      frames = []
      for date in dates:
          # Slice to trips active on date
          ids = a.loc[a[date] == 1, "trip_id"]
          f = t[t["trip_id"].isin(ids)].copy()
          f["date"] = date
          # Groupby trip ID and sort groups by their minimum departure time.
          # For some reason NaN departure times mess up the transform below.
          # So temporarily fill NaN departure times as a workaround.
          f["dt"] = f["departure_time"].fillna(method="ffill")
          f["min_dt"] = f.groupby("trip_id")["dt"].transform(min)
          frames.append(f)
  
      f = pd.concat(frames)
      return f.sort_values(["date", "min_dt", "stop_sequence"]).drop(
          ["min_dt", "dt"], axis=1
      )