DATA SOURCE:
The dataset used for this analysis was obtained from the
Cyclistics company database for the year 2019 provided
by Google/Coursera to solve real-world case
studies.
Here is the dataset for your reference Click
to view
Note: I earlier did a Data cleaning in Excel before importing in R
The business task is to maximize the number of annual members by converting casual riders into annual members.
The object is to compare the usage patterns of annual members and casual riders of Cyclistic bike-sharing service. By analyzing metrics like trip duration, frequency, and usage times, I aim to identify key factors that can help convert casual riders into annual members, thereby increasing annual memberships. This analysis will provide insights for strategies to boost membership conversion and retention.
Note: Setting up my R environment by loading tidyverse,
skimr, janitor, dplyr,
dplR, ggplot2, lubridate, and
readr packages
library(tidyverse)
library(skimr)
library(janitor)
library(ggplot2)
library(lubridate)
library(dplyr)
library(dplR)
library(readxl)
library(readr)
Loading and Exploring the Data: Understand the structure and content of
the dataset.
## Rows: 365069 Columns: 12
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (6): start_time, end_time, from_station_name, to_station_name, usertype,...
## dbl (5): trip_id, bikeid, from_station_id, to_station_id, birthyear
## num (1): tripduration
##
## ℹ 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.
head and
summary function
head(Cyclistic_data, n=10)
## # A tibble: 10 × 12
## trip_id start_time end_time bikeid tripduration from_station_id
## <dbl> <chr> <chr> <dbl> <dbl> <dbl>
## 1 21742443 1/1/2019 0:04 1/1/2019 0:11 2167 390 199
## 2 21742444 1/1/2019 0:08 1/1/2019 0:15 4386 441 44
## 3 21742445 1/1/2019 0:13 1/1/2019 0:27 1524 829 15
## 4 21742446 1/1/2019 0:13 1/1/2019 0:43 252 1783 123
## 5 21742447 1/1/2019 0:14 1/1/2019 0:20 1170 364 173
## 6 21742448 1/1/2019 0:15 1/1/2019 0:19 2437 216 98
## 7 21742449 1/1/2019 0:16 1/1/2019 0:19 2708 177 98
## 8 21742450 1/1/2019 0:18 1/1/2019 0:20 2796 100 211
## 9 21742451 1/1/2019 0:18 1/1/2019 0:47 6205 1727 150
## 10 21742452 1/1/2019 0:19 1/1/2019 0:24 3939 336 268
## # ℹ 6 more variables: from_station_name <chr>, to_station_id <dbl>,
## # to_station_name <chr>, usertype <chr>, gender <chr>, birthyear <dbl>
summary(Cyclistic_data)
## trip_id start_time end_time bikeid
## Min. :21742443 Length:365069 Length:365069 Min. : 1
## 1st Qu.:21848765 Class :character Class :character 1st Qu.:1777
## Median :21961829 Mode :character Mode :character Median :3489
## Mean :21960872 Mean :3429
## 3rd Qu.:22071823 3rd Qu.:5157
## Max. :22178528 Max. :6471
## tripduration from_station_id from_station_name to_station_id
## Min. : 61 Min. : 2.0 Length:365069 Min. : 2.0
## 1st Qu.: 326 1st Qu.: 76.0 Class :character 1st Qu.: 76.0
## Median : 524 Median :170.0 Mode :character Median :168.0
## Mean : 1016 Mean :198.1 Mean :198.6
## 3rd Qu.: 866 3rd Qu.:287.0 3rd Qu.:287.0
## Max. :10628400 Max. :665.0 Max. :665.0
## to_station_name usertype gender birthyear
## Length:365069 Length:365069 Length:365069 Min. :1900
## Class :character Class :character Class :character 1st Qu.:1976
## Mode :character Mode :character Mode :character Median :1985
## Mean :1982
## 3rd Qu.:1991
## Max. :2003
Note: Check for missing values. Here i found out there is an NA value at
the gender column
colSums(is.na(Cyclistic_data))
## trip_id start_time end_time bikeid
## 0 0 0 0
## tripduration from_station_id from_station_name to_station_id
## 0 0 0 0
## to_station_name usertype gender birthyear
## 0 0 19711 0
Replace NA values with “Not applicable” in gender column
Cyclistic_data <- Cyclistic_data %>%
mutate(gender = ifelse(is.na(gender), "Not applicable", gender))
Re-check for NA values
colSums(is.na(Cyclistic_data))
## trip_id start_time end_time bikeid
## 0 0 0 0
## tripduration from_station_id from_station_name to_station_id
## 0 0 0 0
## to_station_name usertype gender birthyear
## 0 0 0 0
Convert date columns to date type
Cyclistic_data$start_time <- as.POSIXct(Cyclistic_data$start_time, format="%m/%d/%Y %H:%M")
Cyclistic_data$end_time <- as.POSIXct(Cyclistic_data$end_time, format="%m/%d/%Y %H:%M")
Note: I create new variables: rider_length,
day_of_week and hour_of_day
Cyclistic_data <- Cyclistic_data %>%
mutate(rider_length = as.numeric(difftime(end_time, start_time, units = "mins")),
hour_of_day = hour(start_time),
day_of_week = wday(start_time, label = TRUE),
start_month = month(start_time, label = TRUE))
print(Cyclistic_data, n=10)
## # A tibble: 365,069 × 16
## trip_id start_time end_time bikeid tripduration
## <dbl> <dttm> <dttm> <dbl> <dbl>
## 1 21742443 2019-01-01 00:04:00 2019-01-01 00:11:00 2167 390
## 2 21742444 2019-01-01 00:08:00 2019-01-01 00:15:00 4386 441
## 3 21742445 2019-01-01 00:13:00 2019-01-01 00:27:00 1524 829
## 4 21742446 2019-01-01 00:13:00 2019-01-01 00:43:00 252 1783
## 5 21742447 2019-01-01 00:14:00 2019-01-01 00:20:00 1170 364
## 6 21742448 2019-01-01 00:15:00 2019-01-01 00:19:00 2437 216
## 7 21742449 2019-01-01 00:16:00 2019-01-01 00:19:00 2708 177
## 8 21742450 2019-01-01 00:18:00 2019-01-01 00:20:00 2796 100
## 9 21742451 2019-01-01 00:18:00 2019-01-01 00:47:00 6205 1727
## 10 21742452 2019-01-01 00:19:00 2019-01-01 00:24:00 3939 336
## # ℹ 365,059 more rows
## # ℹ 11 more variables: from_station_id <dbl>, from_station_name <chr>,
## # to_station_id <dbl>, to_station_name <chr>, usertype <chr>, gender <chr>,
## # birthyear <dbl>, rider_length <dbl>, hour_of_day <int>, day_of_week <ord>,
## # start_month <ord>
Statistical analysis: I generated descriptive statistics and look for
interesting patterns.
summary(Cyclistic_data$rider_length)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1.00 5.00 9.00 16.94 14.00 177200.00
summary(Cyclistic_data$day_of_week)
## Sun Mon Tue Wed Thu Fri Sat
## 27999 50399 61005 60414 66903 63047 35302
summary(Cyclistic_data$hour_of_day)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.00 9.00 14.00 13.34 17.00 23.00
summary(Cyclistic_data$start_month, 4)
## Mar Jan Feb (Other)
## 165611 103272 96186 0
top_start_stations <- Cyclistic_data %>%
count(from_station_name, sort = TRUE) %>%
top_n(5)
## Selecting by n
top_start_stations
## # A tibble: 5 × 2
## from_station_name n
## <chr> <int>
## 1 Clinton St & Washington Blvd 7699
## 2 Clinton St & Madison St 6565
## 3 Canal St & Adams St 6342
## 4 Columbus Dr & Randolph St 4655
## 5 Canal St & Madison St 4571
Rider length by user type: This plot compares trip
durations across user types, revealing that casual riders generally have
longer trip durations compared to members.
Distribution of rider_length: This histogram displays
the distribution of trip durations, indicating that the majority of
trips for both user types are short, with most lasting less than 30
minutes.
Number of ride per hour of the day: This plot
illustrates the peak usage hours for riders, showing a higher frequency
of trips during afternoon and evening hours for casual
riders, whereas members exhibit peak usage in
the morning and evening.
Number of ride per day of the week: This bar chart
depicts the weekly riding frequency by user type, highlighting that
casual riders predominantly ride on Saturdays and
Sundays, while members have a higher riding frequency
on weekdays.
Number of ride by month: This bar chart illustrates a
seasonal trend for the first quartile, showing a decline in rider
activity in February and an increment in
March.
Average ride duration by day of the week: This plot
presents the average trip duration by day of the week, indicating that
Thursday has the highest average trip duration.
Analysis of trip durations and riding patterns reveals distinct user behavior between casual riders and members. While short trips (under 30 minutes) dominate both groups, casual riders demonstrate a propensity for longer journeys. Peak usage times further differentiate the user base. Casual riders exhibit a preference for afternoons and evenings, whereas members favor mornings and evenings. Weekly riding patterns diverge as well, with casual riders gravitating towards weekends, especially Saturdays and Sundays. Conversely, members tend to ride more frequently on weekdays. Seasonal trends highlight a decrease in activity during February, followed by a subsequent rise in March. Notably, average trip durations reach a peak on Thursdays, suggesting specific mid-week riding habits. These insights present valuable opportunities for targeted service and engagement strategies to enhance user satisfaction and optimize operational efficiency for both casual riders and members.
This report outlines a multi-faceted marketing strategy designed to
leverage user data and convert casual riders into annual members. My
analysis of trip durations and riding patterns has yielded several key
insights that inform these targeted initiatives:
Cost Savings for Frequent Riders: I have observed that casual riders often take longer trips. Capitalizing on this data point, I emphasize the significant cost benefits an annual membership offers for frequent long-distance riders. Comparative cost analyses will be presented to clearly demonstrate potential savings.
Peak Hour Promotions: The data suggests casual riders primarily utilize the service during afternoon and evening hours. To capture their attention during peak usage times, promotions and advertisements for membership plans should be strategically placed during these periods.
Weekend Membership Campaigns: Casual riders exhibit a clear preference for riding on Saturdays and Sundays. Weekend-only promotions or special offers for annual memberships should be launched to capitalize on this trend and entice riders during their peak usage days.
Seasonal Incentives: Recognizing a decline in rider activity during February and a subsequent increase in March, i propose offering limited-time discounts on annual memberships at the end of winter. This incentivizes casual riders to commit to a membership as the weather improves and riding frequency rises.
Promoting Weekday Benefits: As members tend to ride more on weekdays, we will highlight the advantages of membership for those considering increased riding during the workweek. Campaigns showcasing the convenience and cost savings for regular commuters will be implemented.
Leveraging High Trip Duration Days: With Thursdays exhibiting the highest average trip duration, we will create targeted campaigns specifically tailored to this day. “Thursday Membership Deals” will incentivize casual riders taking longer trips to convert to annual memberships.
The Power of Social Proof: Customer testimonials and success stories from current members who transitioned from being casual riders will be shared. These narratives will highlight member satisfaction with the program’s benefits, emphasizing cost savings, convenience, and overall value.
By implementing these data-driven strategies, the company can effectively target casual riders, addressing their specific usage patterns and motivations. This comprehensive approach will ultimately lead to a significant increase in annual membership conversions.