Recreating Septa Transit Timetables in R

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.4     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(gt)
library(hms)

Attaching package: 'hms'

The following object is masked from 'package:lubridate':

    hms
stops <- 
  read_csv("chw-stops.csv")
Rows: 14 Columns: 11
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (3): fare_zone, stop_name, zone_id
dbl (6): service_access, service_cash, service_park, stop_id, stop_lat, stop...
lgl (2): stop_desc, stop_url

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
times <-
  read_csv("times.csv")
Rows: 14 Columns: 9
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (1): stop_name
time (8): 8210, 8242, 8318, 8322, 8338, 8716, 8750, 8756

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
stop_times <- 
  times %>%
  left_join(stops, by = "stop_name")
stop_times %>%
  mutate(across(matches("^[0-9]{4}$"), as.POSIXct)) %>%
  gt() %>%
  cols_hide(c(
    "stop_url", "zone_id", "stop_desc", "stop_lat", "stop_lon", "stop_id")) %>%
  fmt_time(
    columns = matches("^[0-9]{4}$"),
    time_style = "h_m_p") %>%
  fmt(
    columns = starts_with("service_"),
    fns = ~if_else(as.logical(.), "&check;", "")) %>%
  cols_label(
    stop_name = "Stations",
    service_access = "A",
    service_cash = "C",
    service_park = "P",
    fare_zone = html("Fare<br>Zone")) %>%
  tab_spanner(
    columns = starts_with("service_"),
    label = "Services") %>%
  tab_spanner(
    columns = matches("^[0-9]{4}$"),
    label = "Train Number") %>%
  cols_move_to_start("fare_zone") %>%
  cols_move_to_start(starts_with("service_")) %>%
  cols_width(
    starts_with("service_") ~ px(20),
    starts_with("8") ~ px(80)) %>%
  opt_row_striping(row_striping = T) %>%
  cols_align(
    columns = "fare_zone",
    align = "center") %>%
  cols_align(
    columns = matches("^[0-9]{4}$"),
    align = "right") %>%
  tab_style(
    locations = cells_body(rows = c(11:13)),
    style = list(
      cell_fill(color = "black"), 
      cell_text(color = "white"),
      cell_borders(
        sides = c("top", "bottom"),
        style = "hidden"))) %>%
  tab_style(
    locations = cells_body(
      columns = !matches("^[0-9]{4}$"),
      rows = c(11:13)),
    style = css(
      border_top = "none !important;",
      border_bottom = "none !important;",
      border_right = "solid white 2px !important;",
      color = "white !important;")) %>%
  tab_style(
    locations = cells_body(
      columns = !matches("^[0-9]{4}$"),
      rows = c(1:10, 14)),
    style = cell_borders(
      sides = "right",
      color = "black",
      style = "solid",
      weight = px(2))) %>%
  tab_options(
    row.striping.background_color = "#A9A9A9") %>%
  opt_table_font(font = google_font("IBM Plex Sans"))
Services
Fare
Zone
Stations
Train Number
A C P 8210 8242 8318 8322 8338 8716 8750 8756
2 Chestnut Hill West 6:51 AM 2:49 PM 8:49 AM 9:49 AM 1:52 PM 8:08 AM 4:48 PM 6:20 PM
2 Highland 6:52 AM 2:50 PM 8:50 AM 9:50 AM 1:53 PM 8:09 AM 4:49 PM 6:21 PM
1 St. Martins 6:54 AM 2:52 PM 8:52 AM 9:52 AM 1:55 PM 8:11 AM 4:51 PM 6:23 PM
1 Richard Allen Lane 6:56 AM 2:54 PM 8:54 AM 9:54 AM 1:57 PM 8:13 AM 4:53 PM 6:25 PM
1 Carpenter 6:58 AM 2:56 PM 8:56 AM 9:56 AM 1:59 PM 8:15 AM 4:55 PM 6:27 PM
1 Upsal 7:00 AM 2:58 PM 8:58 AM 9:58 AM 2:01 PM 8:17 AM 4:57 PM 6:29 PM
C Tulpehocken 7:02 AM 3:00 PM 9:00 AM 10:00 AM 2:03 PM 8:19 AM 4:59 PM 6:31 PM
C Chelten Avenue 7:04 AM 3:02 PM 9:02 AM 10:02 AM 2:05 PM 8:21 AM 5:01 PM 6:33 PM
C Queen Lane 7:06 AM 3:04 PM 9:04 AM 10:04 AM 2:07 PM 8:23 AM 5:03 PM 6:35 PM
C North Philadelphia 7:12 AM 3:12 PM 9:12 AM 10:12 AM 2:15 PM 8:29 AM 5:09 PM 6:41 PM
2 Gray 30th Street 7:23 AM 3:23 PM 9:23 AM 10:23 AM 2:26 PM 8:42 AM 5:20 PM 6:54 PM
2 Suburban Station 7:28 AM 3:28 PM 9:28 AM 10:28 AM 2:31 PM 8:47 AM 5:25 PM 6:59 PM
2 Jefferson Station 7:33 AM 3:33 PM 9:33 AM 10:33 AM 2:36 PM 8:52 AM 5:30 PM 7:04 PM
2 Temple University 7:37 AM 3:37 PM 9:37 AM 10:37 AM 2:40 PM 8:57 AM 5:35 PM 7:08 PM