Beachhead Assignment #1

Introduction

I’m using the MTA Subway Hourly Ridership dataset (2020–2024) from data.ny.gov, chosen for its relevance to my work at NYCT and because it needs to be cleaned up before we can draw any conclusions. Ridership will serve as the target variable. Since this dataset is very large at over 500,000 rows, I have queried it so it is much more simple to work and with and publish by limiting it to the month of July 2024 consisting of riders with at least 1 transfer.

To tackle the problem, I will parse the timestamp into a proper datetime, drop redundant columns, split any categorical fields into separate variables, and subset to a clean, well-named set of columns.

The data challenges I anticipate include a string-formatted timestamp that needs to be parsed and separated to increase the ways of sorting and filtering through the data, redundant or overlapping columns, a compound categorical field that conflates two variables when they don’t relate, a large row count that may need filtering, and multiple transit modes that will require a scoping decision.

Code deliverables

Loading libraries and raw data

First, the necessary libraries must be loaded in order to load the dataset and access it.

library(tidyverse)
Warning: package 'tidyverse' was built under R version 4.6.1
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.2.1     ✔ readr     2.2.0
✔ forcats   1.0.1     ✔ stringr   1.6.0
✔ ggplot2   4.0.3     ✔ tibble    3.3.1
✔ lubridate 1.9.5     ✔ tidyr     1.3.2
✔ purrr     1.2.2     
── 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
dataMta <- read_csv("https://raw.githubusercontent.com/daanishrasheed/DATA607/main/Assignment%201/MTA_Subway_Hourly_Ridership__7_2024.csv", show_col_types = FALSE)
Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)
glimpse(dataMta)
Rows: 566,399
Columns: 12
$ transit_timestamp   <chr> "07/25/2024 07:00:00 AM", "07/11/2024 05:00:00 PM"…
$ transit_mode        <chr> "staten_island_railway", "staten_island_railway", …
$ station_complex_id  <chr> "502", "501", "501", "501", "501", "501", "501", "…
$ station_complex     <chr> "Tompkinsville (SIR)", "St George (SIR)", "St Geor…
$ borough             <chr> "Staten Island", "Staten Island", "Staten Island",…
$ payment_method      <chr> "omny", "omny", "omny", "metrocard", "metrocard", …
$ fare_class_category <chr> "OMNY - Full Fare", "OMNY - Seniors & Disability",…
$ ridership           <dbl> 17, 7, 29, 9, 6, 48, 1, 2, 17, 37, 6, 320, 346, 7,…
$ transfers           <dbl> 2, 3, 16, 3, 2, 5, 1, 1, 6, 4, 2, 29, 63, 1, 1, 1,…
$ latitude            <dbl> 40.63695, 40.64375, 40.64375, 40.64375, 40.64375, …
$ longitude           <dbl> -74.07484, -74.07365, -74.07365, -74.07365, -74.07…
$ Georeference        <chr> "POINT (-74.07484 40.636948)", "POINT (-74.07365 4…

Select all necessary columns

As mentioned, the target variable is ‘ridership’, so we need to shorten the dataset to include only the information that has an impact.

subset <- dataMta %>%
  select(
    transit_timestamp,
    station_complex,
    borough,
    transit_mode,
    payment_method,
    fare_class_category,
    ridership   # target variable
  )

Cleaning data

The following changes are some of the many that need to be done:

  • Turn “transit_timestamp” to a real date-time object instead of a string.

  • A new column, fare_type, is created from fare_class_category. The “OMNY -” or “Metrocard -” prefix is taken off, so the fare type stands alone. Then the remaining is modified to be shorter: “Full Fare” and “Fair Fare” will remove the ‘fare’, but I will clarify that ‘Fair’ means reduced. All other fields in the column stays the same.

  • Columns are renamed to more expressive names: transit_timestamp to date_time, transit_mode to transit_type, and payment_method to fare_payment_method.

  • Also make sure ridership is the last column as it is the target.

MTAClean <- subset %>%
  mutate(
    transit_timestamp = parse_date_time(transit_timestamp, "mdY IMS p"),
    fare_type = fare_class_category %>%
      str_remove("^(OMNY|Metrocard)\\s*-\\s*") %>%
      case_match(
        "Full Fare" ~ "Full",
        "Fair Fare" ~ "Fair (Reduced)",
        "Seniors & Disability" ~ "Senior & Disability",
        "Students" ~ "Students",
        "Other" ~ "Other",
        .default = fare_class_category
      )
  ) %>%
  select(-fare_class_category) %>%
  rename(
    date_time = transit_timestamp,
    transit_type = transit_mode,
    fare_payment_method = payment_method
  ) %>%
  relocate(ridership, .after = last_col())
Warning: There was 1 warning in `mutate()`.
ℹ In argument: `fare_type = `%>%`(...)`.
Caused by warning:
! `case_match()` was deprecated in dplyr 1.2.0.
ℹ Please use `recode_values()` instead.

Conclusion

The transformed data frame turns the raw MTA Subway Hourly Ridership file into a dataset primed to be analyzed. The combined fare-class string is split into separate payment method and fare type columns, and the timestamp is a proper datetime rather than a string. The target variable, riders, sits at the end of the frame, as it can be possibly predicted using all the other columns.

Analysis on this data can definitely be done on a much larger scale as I only took data for July 2024. Pulling in additional months from the same source would allow seasonal or year-over-year ridership comparisons, and adding back latitude and longitude would allow us to analyze the impact of location.

Also, the metrocard is being used less and less in favor of OMNY, which will make the category more populated with the latter. This will eventually make the column a bit redundant in the future and will be removed if necessary as its impact will be decreased.