0% found this document useful (0 votes)
43 views5 pages

Divvy Bike Usage Analysis: Members vs Casuals

This document summarizes the steps taken to analyze Divvy bike trip data from Chicago for 2019 and the first quarter of 2020. The analysis consolidates trip data from multiple CSV files into a single dataframe, cleans the data by standardizing column names and formats, and adds additional variables like date and ride duration. Descriptive statistics are then calculated to compare how members and casual riders use Divvy bikes differently, such as average ride times by day of the week.

Uploaded by

Gustavo Martino
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
43 views5 pages

Divvy Bike Usage Analysis: Members vs Casuals

This document summarizes the steps taken to analyze Divvy bike trip data from Chicago for 2019 and the first quarter of 2020. The analysis consolidates trip data from multiple CSV files into a single dataframe, cleans the data by standardizing column names and formats, and adds additional variables like date and ride duration. Descriptive statistics are then calculated to compare how members and casual riders use Divvy bikes differently, such as average ride times by day of the week.

Uploaded by

Gustavo Martino
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

### Cyclistic_Exercise_Full_Year_Analysis ###

# This analysis is for case study 1 from the Google Data Analytics Certificate
(Cyclistic). It’s originally based on the case study "'Sophisticated, Clear, and
Polished’: Divvy and Data Visualization" written by Kevin Hartman (found here:
[Link] We will be using the Divvy
dataset for the case study. The purpose of this script is to consolidate downloaded
Divvy data into a single dataframe and then conduct simple analysis to help answer
the key question: “In what ways do members and casual riders use Divvy bikes
differently?”

# # # # # # # # # # # # # # # # # # # # # # #
# Install required packages
# tidyverse for data import and wrangling
# libridate for date functions
# ggplot for visualization
# # # # # # # # # # # # # # # # # # # # # # #

library(tidyverse) #helps wrangle data


library(lubridate) #helps wrangle date attributes
library(ggplot2) #helps visualize data
getwd() #displays your working directory
setwd("/Users/kevinhartman/Desktop/Divvy_Exercise/csv") #sets your working
directory to simplify calls to data ... make sure to use your OWN username instead
of mine ;)

#=====================
# STEP 1: COLLECT DATA
#=====================
# Upload Divvy datasets (csv files) here
q2_2019 <- read_csv("Divvy_Trips_2019_Q2.csv")
q3_2019 <- read_csv("Divvy_Trips_2019_Q3.csv")
q4_2019 <- read_csv("Divvy_Trips_2019_Q4.csv")
q1_2020 <- read_csv("Divvy_Trips_2020_Q1.csv")

#====================================================
# STEP 2: WRANGLE DATA AND COMBINE INTO A SINGLE FILE
#====================================================
# Compare column names each of the files
# While the names don't have to be in the same order, they DO need to match
perfectly before we can use a command to join them into one file
colnames(q3_2019)
colnames(q4_2019)
colnames(q2_2019)
colnames(q1_2020)

# Rename columns to make them consisent with q1_2020 (as this will be the supposed
going-forward table design for Divvy)

(q4_2019 <- rename(q4_2019


,ride_id = trip_id
,rideable_type = bikeid
,started_at = start_time
,ended_at = end_time
,start_station_name = from_station_name
,start_station_id = from_station_id
,end_station_name = to_station_name
,end_station_id = to_station_id
,member_casual = usertype))

(q3_2019 <- rename(q3_2019


,ride_id = trip_id
,rideable_type = bikeid
,started_at = start_time
,ended_at = end_time
,start_station_name = from_station_name
,start_station_id = from_station_id
,end_station_name = to_station_name
,end_station_id = to_station_id
,member_casual = usertype))

(q2_2019 <- rename(q2_2019


,ride_id = "01 - Rental Details Rental ID"
,rideable_type = "01 - Rental Details Bike ID"
,started_at = "01 - Rental Details Local Start Time"
,ended_at = "01 - Rental Details Local End Time"
,start_station_name = "03 - Rental Start Station Name"
,start_station_id = "03 - Rental Start Station ID"
,end_station_name = "02 - Rental End Station Name"
,end_station_id = "02 - Rental End Station ID"
,member_casual = "User Type"))

# Inspect the dataframes and look for inconguencies


str(q1_2020)
str(q4_2019)
str(q3_2019)
str(q2_2019)

# Convert ride_id and rideable_type to character so that they can stack correctly
q4_2019 <- mutate(q4_2019, ride_id = [Link](ride_id)
,rideable_type = [Link](rideable_type))
q3_2019 <- mutate(q3_2019, ride_id = [Link](ride_id)
,rideable_type = [Link](rideable_type))
q2_2019 <- mutate(q2_2019, ride_id = [Link](ride_id)
,rideable_type = [Link](rideable_type))

# Stack individual quarter's data frames into one big data frame
all_trips <- bind_rows(q2_2019, q3_2019, q4_2019, q1_2020)

# Remove lat, long, birthyear, and gender fields as this data was dropped beginning
in 2020
all_trips <- all_trips %>%
select(-c(start_lat, start_lng, end_lat, end_lng, birthyear, gender, "01 - Rental
Details Duration In Seconds Uncapped", "05 - Member Details Member Birthday Year",
"Member Gender", "tripduration"))
#======================================================
# STEP 3: CLEAN UP AND ADD DATA TO PREPARE FOR ANALYSIS
#======================================================
# Inspect the new table that has been created
colnames(all_trips) #List of column names
nrow(all_trips) #How many rows are in data frame?
dim(all_trips) #Dimensions of the data frame?
head(all_trips) #See the first 6 rows of data frame. Also tail(qs_raw)
str(all_trips) #See list of columns and data types (numeric, character, etc)
summary(all_trips) #Statistical summary of data. Mainly for numerics

# There are a few problems we will need to fix:


# (1) In the "member_casual" column, there are two names for members ("member" and
"Subscriber") and two names for casual riders ("Customer" and "casual"). We will
need to consolidate that from four to two labels.
# (2) The data can only be aggregated at the ride-level, which is too granular. We
will want to add some additional columns of data -- such as day, month, year --
that provide additional opportunities to aggregate the data.
# (3) We will want to add a calculated field for length of ride since the 2020Q1
data did not have the "tripduration" column. We will add "ride_length" to the
entire dataframe for consistency.
# (4) There are some rides where tripduration shows up as negative, including
several hundred rides where Divvy took bikes out of circulation for Quality Control
reasons. We will want to delete these rides.

# In the "member_casual" column, replace "Subscriber" with "member" and "Customer"


with "casual"
# Before 2020, Divvy used different labels for these two types of riders ... we
will want to make our dataframe consistent with their current nomenclature
# N.B.: "Level" is a special property of a column that is retained even if a subset
does not contain any values from a specific level
# Begin by seeing how many observations fall under each usertype
table(all_trips$member_casual)

# Reassign to the desired values (we will go with the current 2020 labels)
all_trips <- all_trips %>%
mutate(member_casual = recode(member_casual
,"Subscriber" = "member"
,"Customer" = "casual"))

# Check to make sure the proper number of observations were reassigned


table(all_trips$member_casual)

# Add columns that list the date, month, day, and year of each ride
# This will allow us to aggregate ride data for each month, day, or year ... before
completing these operations we could only aggregate at the ride level
# [Link] more on date formats in R found at
that link
all_trips$date <- [Link](all_trips$started_at) #The default format is yyyy-mm-dd
all_trips$month <- format([Link](all_trips$date), "%m")
all_trips$day <- format([Link](all_trips$date), "%d")
all_trips$year <- format([Link](all_trips$date), "%Y")
all_trips$day_of_week <- format([Link](all_trips$date), "%A")

# Add a "ride_length" calculation to all_trips (in seconds)


# [Link]
all_trips$ride_length <- difftime(all_trips$ended_at,all_trips$started_at)

# Inspect the structure of the columns


str(all_trips)

# Convert "ride_length" from Factor to numeric so we can run calculations on the


data
[Link](all_trips$ride_length)
all_trips$ride_length <- [Link]([Link](all_trips$ride_length))
[Link](all_trips$ride_length)

# Remove "bad" data


# The dataframe includes a few hundred entries when bikes were taken out of docks
and checked for quality by Divvy or ride_length was negative
# We will create a new version of the dataframe (v2) since data is being removed
# [Link]
all_trips_v2 <- all_trips[!(all_trips$start_station_name == "HQ QR" |
all_trips$ride_length<0),]

#=====================================
# STEP 4: CONDUCT DESCRIPTIVE ANALYSIS
#=====================================
# Descriptive analysis on ride_length (all figures in seconds)
mean(all_trips_v2$ride_length) #straight average (total ride length / rides)
median(all_trips_v2$ride_length) #midpoint number in the ascending array of ride
lengths
max(all_trips_v2$ride_length) #longest ride
min(all_trips_v2$ride_length) #shortest ride

# You can condense the four lines above to one line using summary() on the specific
attribute
summary(all_trips_v2$ride_length)

# Compare members and casual users


aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual, FUN = mean)
aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual, FUN = median)
aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual, FUN = max)
aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual, FUN = min)

# See the average ride time by each day for members vs casual users
aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual +
all_trips_v2$day_of_week, FUN = mean)

# Notice that the days of the week are out of order. Let's fix that.
all_trips_v2$day_of_week <- ordered(all_trips_v2$day_of_week, levels=c("Sunday",
"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"))
# Now, let's run the average ride time by each day for members vs casual users
aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual +
all_trips_v2$day_of_week, FUN = mean)

# analyze ridership data by type and weekday


all_trips_v2 %>%
mutate(weekday = wday(started_at, label = TRUE)) %>% #creates weekday field
using wday()
group_by(member_casual, weekday) %>% #groups by usertype and weekday
summarise(number_of_rides = n()
#calculates the number of rides and average duration
,average_duration = mean(ride_length)) %>% # calculates the
average duration
arrange(member_casual, weekday)
# sorts

# Let's visualize the number of rides by rider type


all_trips_v2 %>%
mutate(weekday = wday(started_at, label = TRUE)) %>%
group_by(member_casual, weekday) %>%
summarise(number_of_rides = n()
,average_duration = mean(ride_length)) %>%
arrange(member_casual, weekday) %>%
ggplot(aes(x = weekday, y = number_of_rides, fill = member_casual)) +
geom_col(position = "dodge")

# Let's create a visualization for average duration


all_trips_v2 %>%
mutate(weekday = wday(started_at, label = TRUE)) %>%
group_by(member_casual, weekday) %>%
summarise(number_of_rides = n()
,average_duration = mean(ride_length)) %>%
arrange(member_casual, weekday) %>%
ggplot(aes(x = weekday, y = average_duration, fill = member_casual)) +
geom_col(position = "dodge")

#=================================================
# STEP 5: EXPORT SUMMARY FILE FOR FURTHER ANALYSIS
#=================================================
# Create a csv file that we will visualize in Excel, Tableau, or my presentation
software
# N.B.: This file location is for a Mac. If you are working on a PC, change the
file location accordingly (most likely "C:\Users\YOUR_USERNAME\Desktop\...") to
export the data. You can read more here: [Link]
to-csv-in-r/
counts <- aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual +
all_trips_v2$day_of_week, FUN = mean)
[Link](counts, file = '~/Desktop/Divvy_Exercise/avg_ride_length.csv')

#You're done! Congratulations!

Common questions

Powered by AI

The analysis doesn't explicitly differentiate between day and night usage patterns within the provided steps. However, 'started_at' and 'ended_at' timestamps could be used to infer time-based usage patterns. Calculating usage during typical daytime versus nighttime hours by filtering the data can provide insights into the temporal usage behaviors of different user types .

Columns for 'date', 'month', 'day', 'year', and 'day_of_week' were added to allow aggregation of ride data beyond the ride level. Additionally, a calculated field for 'ride_length' was introduced since the 2020Q1 data did not include a 'tripduration' column .

To handle inconsistencies in the 'member_casual' column labels, the data was normalized by replacing 'Subscriber' with 'member' and 'Customer' with 'casual'. This was done to align with Divvy's current nomenclature. The script used R's 'mutate' function combined with 'recode' to reassign these values, ensuring consistency across the dataset .

To convert casual riders into members, strategies could be developed based on identified patterns such as offering targeted promotions during peak casual usage times or days, enhancing membership benefits during frequently used days like weekends, and possibly extending services tailored to the needs identified in casual user's ride length and frequency patterns. Understanding motivations and ride purposes can guide specific membership incentives .

The 'day_of_week' column was converted into an ordered factor to ensure the days appear in chronological order. This was achieved using R's 'ordered' function, specifying the correct sequence of days starting from Sunday to Saturday .

The analysis uses bar charts to visualize the number of rides by rider type across weekdays. The 'ggplot2' package is used to create these visualizations, specifically using the 'geom_col' function to display bars filled by 'member_casual' for each 'weekday'. Additionally, average ride durations are visualized using a similar technique .

Exporting summary data to a CSV file allows the insights to be easily shared and further analyzed using other software tools such as Excel or Tableau. It facilitates visualization and presentation of the analytical findings in a format that can be accessed or manipulated by non-technical stakeholders .

Bad data was removed based on specific conditions: entries where 'ride_length' was negative, or when bikes were taken out for quality checks (indicated by the 'start_station_name' being 'HQ QR') were excluded from the final version of the dataframe .

The steps taken to consolidate data include: 1) Collecting data by uploading Divvy datasets (Q2 2019 to Q1 2020) into separate data frames. 2) Wrangling data by renaming columns to achieve consistency across dataframes. 3) Ensuring the columns are of the correct type by converting 'ride_id' and 'rideable_type' to character type. 4) Stacking the individual quarter data frames into one big dataframe called 'all_trips'. Additional unnecessary columns that were no longer used were removed .

Members have a higher average ride length than casual users. The mean ride length for members is shorter compared to casual riders, indicating that members generally take shorter rides. However, both average and median calculations can be applied to compare the overall ride durations for more detailed insights .

You might also like