You are a junior data analyst working on the marketing analyst team at Cyclistic, a bike-share company in Chicago. The director of marketing believes the company’s future success depends on maximizing the number of annual memberships. Therefore, your team wants to understand how casual riders and annual members use Cyclistic bikes differently. From these insights, your team will design a new marketing strategy to convert casual riders into annual members. But first, Cyclistic executives must approve your recommendations, so they must be backed up with compelling data insights and professional data visualizations.
In 2016, Cyclistic launched a successful bike-share offering. Since then, the program has grown to a fleet of 5,824 bicycles that are geotracked and locked into a network of 692 stations across Chicago. The bikes can be unlocked from one station and returned to any other station in the system anytime. Until now, Cyclistic’s marketing strategy relied on building general awareness and appealing to broad consumer segments. One approach that helped make these things possible was the flexibility of its pricing plans: single-ride passes, full-day passes, and annual memberships. Customers who purchase single-ride or full-day passes are referred to as casual riders. Customers who purchase annual memberships are Cyclistic members. Cyclistic’s finance analysts have concluded that annual members are much more profitable than casual riders. Although the pricing flexibility helps Cyclistic attract more customers, Moreno believes that maximizing the number of annual members will be key to future growth. Rather than creating a marketing campaign that targets all-new customers, Moreno believes there is a solid opportunity to convert casual riders into members. She notes that casual riders are already aware of the Cyclistic program and have chosen Cyclistic for their mobility needs. Moreno has set a clear goal: Design marketing strategies aimed at converting casual riders into annual members. In order to do that, however, the team needs to better understand how annual members and casual riders differ, why casual riders would buy a membership, and how digital media could affect their marketing tactics. Moreno and her team are interested in analyzing the Cyclistic historical bike trip data to identify trends.
The business task is to design marketing strategies aimed at converting casual riders into annual members. To achieve this task, there need to be understanding on the differences between casual riders and member, hence this project
The trips data of the Cyclistic which was made available by Motivate International Inc. and were downloaded from [https://divvy-tripdata.s3.amazonaws.com/index.html]. The downloaded data in the various months’ zip folders were unzipped into a folder named Cyclistic_unzipped .15 current datasets from 2022 to 2024 were selected into another folder named cyclistic
#Documentation of any cleaning or manipulation of data
# Loading Packages for work.
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.3 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ purrr::%||%() masks base::%||%()
## ✖ 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(dplyr)
library(lubridate)
library("hms")
##
## Attaching package: 'hms'
##
## The following object is masked from 'package:lubridate':
##
## hms
# Importing Data and combining them
setwd("C:/Users/Daniel/Desktop/Cyclistic") # setting work directory
fnames <- list.files() # listing files
print(fnames)
## [1] "202201-divvy-tripdata.csv" "202202-divvy-tripdata.csv"
## [3] "202203-divvy-tripdata.csv" "202204-divvy-tripdata.csv"
## [5] "202206-divvy-tripdata.csv" "202307-divvy-tripdata.csv"
## [7] "202308-divvy-tripdata.csv" "202309-divvy-tripdata.csv"
## [9] "202310-divvy-tripdata.csv" "202311-divvy-tripdata.csv"
## [11] "202312-divvy-tripdata.csv" "202401-divvy-tripdata.csv"
## [13] "202402-divvy-tripdata.csv" "202403-divvy-tripdata.csv"
## [15] "202404-divvy-tripdata.csv"
csv <- lapply(fnames, read.csv) # reading files
Cyclistic_data <- do.call(rbind,csv) # Combining Files
# Data Wrangling
Cyclistic_data$started_at <- as.POSIXct(Cyclistic_data$started_at, format = "%Y-%m-%d %H:%M:%S") # converting string started_at to POSIXct format
Cyclistic_data$ended_at <- as.POSIXct(Cyclistic_data$ended_at, format = "%Y-%m-%d %H:%M:%S") # converting string ended_at to POSIXct format
Cyclistic_data$ride_length <- difftime(Cyclistic_data$ended_at,Cyclistic_data$started_at) # finding the difference in between started_at and ended_at
Cyclistic_data$ride_length <- as.numeric(Cyclistic_data$ride_length)
# storing the data$ride_length as numeric
Cyclistic_data$ride_length <- as_hms(Cyclistic_data$ride_length) # storing the answer as hour minute and second
# Cleaning Data
Cyclistic_data <- Cyclistic_data [,- c(5,6,7,8,9,10,11,12)] # remove unwanted columns by column index
Cyclistic_data <- na.omit(Cyclistic_data) # remove empty cells
Cyclistic_data <- distinct(Cyclistic_data) # select distinct rows
Cyclistic_data <- subset(Cyclistic_data,ride_length > 0) # select only rows with ride_length greater than zero
# adding new columns
# convert started_at to date
# convert date to text
# retrive the day of the week as numeric
# convert date to month
# convert date to year
Cyclistic_data <- Cyclistic_data %>% mutate(date = as.Date(started_at),
day_of_week = format( date, "%A"),
day = wday(date), month = format( date, "%m"),
year = format( date, "%Y"))
# A summary of your analysis
#Total Number of Cyclists
Total_number_Users <- count(Cyclistic_data)
Total_number_Users
## n
## 1 6056186
#Mode of Week
library(DescTools)
mode_day_week <- Mode(Cyclistic_data$day_of_week)
mode_day_week
## [1] "Saturday"
## attr(,"freq")
## [1] 933677
#Total number of members and casual
Total_number_Users_pmt <- Cyclistic_data %>% group_by(member_casual)%>% count()
Total_number_Users_pmt
## # A tibble: 2 × 2
## # Groups: member_casual [2]
## member_casual n
## <chr> <int>
## 1 casual 2141735
## 2 member 3914451
#Mean of ride_length
mean_ride_lenght <- as_hms(mean(Cyclistic_data$ride_length))
mean_ride_lenght
## 00:18:30.402014
# Maximum and Mean ride_length by membership type
Mm_Mn_of_ride_length_mt <- Cyclistic_data %>% group_by(member_casual) %>% summarise( mean = as_hms(mean(ride_length)), max = as_hms(max(ride_length)))
Mm_Mn_of_ride_length_mt
## # A tibble: 2 × 3
## member_casual mean max
## <chr> <time> <time>
## 1 casual 29'01.046615" 1641:29:04
## 2 member 12'45.353982" 25:59:56
# Average ride length of users by type of bike
Av_ride_length_users_tb <- Cyclistic_data %>% group_by(member_casual,rideable_type) %>% summarise( mean = as_hms(mean(ride_length)))
## `summarise()` has grouped output by 'member_casual'. You can override using the
## `.groups` argument.
Av_ride_length_users_tb
## # A tibble: 5 × 3
## # Groups: member_casual [2]
## member_casual rideable_type mean
## <chr> <chr> <time>
## 1 casual classic_bike 00:32:54.814899
## 2 casual docked_bike 02:39:27.547238
## 3 casual electric_bike 00:14:54.371702
## 4 member classic_bike 00:14:11.689538
## 5 member electric_bike 00:11:09.652798
#Average ride_length for users by day of week
Av_ride_length_users_dw <- Cyclistic_data %>% group_by(member_casual,day_of_week) %>% summarise( mean = as_hms(mean(ride_length)))
## `summarise()` has grouped output by 'member_casual'. You can override using the
## `.groups` argument.
Av_ride_length_users_dw
## # A tibble: 14 × 3
## # Groups: member_casual [2]
## member_casual day_of_week mean
## <chr> <chr> <time>
## 1 casual Friday 27'42.461208"
## 2 casual Monday 28'34.698235"
## 3 casual Saturday 32'00.074501"
## 4 casual Sunday 33'50.792240"
## 5 casual Thursday 26'05.511689"
## 6 casual Tuesday 26'18.029221"
## 7 casual Wednesday 25'05.929885"
## 8 member Friday 12'29.722483"
## 9 member Monday 12'16.505640"
## 10 member Saturday 14'00.685026"
## 11 member Sunday 14'16.554497"
## 12 member Thursday 12'13.825664"
## 13 member Tuesday 12'21.681553"
## 14 member Wednesday 12'15.515211"
#Number of rides for users by day of the week
No_rides_users_dweek <- Cyclistic_data %>% group_by(member_casual,day_of_week) %>% count()
No_rides_users_dweek
## # A tibble: 14 × 3
## # Groups: member_casual, day_of_week [14]
## member_casual day_of_week n
## <chr> <chr> <int>
## 1 casual Friday 305177
## 2 casual Monday 251825
## 3 casual Saturday 431619
## 4 casual Sunday 360272
## 5 casual Thursday 281415
## 6 casual Tuesday 258002
## 7 casual Wednesday 253425
## 8 member Friday 540139
## 9 member Monday 556010
## 10 member Saturday 502058
## 11 member Sunday 437678
## 12 member Thursday 631441
## 13 member Tuesday 627128
## 14 member Wednesday 619997
You can also embed plots, for example:
# Total Number of Users by Membership Status
ggplot(Total_number_Users_pmt, aes(x = "", y = n, fill = member_casual)) + geom_bar(stat = "identity") + geom_text(aes(label = n), position = position_stack(vjust = 0.5), color = "black", size = 5) + labs(title = "Total Number of Users by Membership Status", x = NULL, y = NULL, fill = "Membership Status") + scale_fill_manual(values = c("#0072B2", "#009E73")) + coord_polar("y", start = 0) + theme_void()
# Mean Ride Length by Membership Status
ggplot(Mm_Mn_of_ride_length_mt, aes(x = member_casual,fill = member_casual, y = mean)) +
geom_bar(stat = "identity", alpha = 0.5) +
labs(title = "Mean Ride Length by Membership Status",
x = "Membership Status",
y = "Mean Ride Length") +
theme_minimal()
# Max Ride Length by Membership Status
ggplot(Mm_Mn_of_ride_length_mt, aes(x = member_casual,fill = member_casual, y = as_hms(max))) +
geom_bar(stat = "identity", alpha = 0.5) +
geom_text(aes(label = max), vjust = -0.5, color = "black", size = 4) +
labs(title = "Max Ride Length by Membership Status",
x = "Membership Status",
y = "Max Ride Length") +
theme_minimal()
# Average Ride Length of Users by Bike Type
ggplot(Av_ride_length_users_tb, aes(x = rideable_type, fill = member_casual, y = mean)) + geom_col(position = "dodge") + labs(title = "Average Ride Length of Users by Bike Type", x = "Rideable Type", y = "Mean Ride Length") + theme_minimal()
# Average Ride Length of Users by Day of the Week
ggplot(Av_ride_length_users_dw, aes(x = day_of_week, fill = member_casual, y = mean)) + geom_col(position = "dodge") + labs(title = "Average Ride Length of Users by Day of the Week", x = "Day of the Week", y = "Mean Ride Length") + theme_minimal() + theme(axis.text.x = element_text(angle = 45, hjust = 1)) # Adjust angle and justification of x-axis labels
# Number of rides for users by day of the week
ggplot(No_rides_users_dweek, aes(x = day_of_week, fill = member_casual, y = n)) + geom_col(position = "dodge") + labs(title = "Number of rides for users by day of the week", x = "Day_of_the_Week", y = "count_of_Ride") + theme_minimal() + theme(axis.text.x = element_text(angle = 45, hjust = 1)) # Adjust angle and justification of x-axis labels
# Stacked graph Number of rides for users by day of the week
ggplot(No_rides_users_dweek, aes(x = day_of_week, fill = member_casual, y = n)) +
geom_col(position = "stack") +
labs(title = "Number of rides for users by day of the week", x = "Day of the Week", y = "Count of Rides") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
#Max Ride Length by Membership Status
ggplot(Mm_Mn_of_ride_length_mt, aes(x = member_casual,fill = member_casual, y = as_hms(max))) +
geom_bar(stat = "identity", alpha = 0.5) +
geom_text(aes(label = max), vjust = -0.5, color = "black", size = 4) +
labs(title = "Max Ride Length by Membership Status",
x = "Membership Status",
y = "Max Ride Length") +
theme_minimal()
2.Since the maximum duration of ride is by the casual user type,the company should employ reward systems and adverts to convert more of the casuals to members.Cyclistic should also offer loyalist packages in the form of discounts to members who form the greatest chunk of users in order to maintain them.