For my Google Data Analytics capstone project, I completed a case study for a fictional bike-share company in which I analyzed 12 months of data to understand the difference between casual riders and annual members. From these insights, the marketing team can develop a strategy to convert casual riders into annual members. To accomplish my task, I applied the data analysis process including ask, prepare, process, analyze, share, and act.

Ask the Right Questions

Cyclistic is a bike-share company in Chicago with a fleet of 5,824 geotracked bicycles and a network of 692 stations. They offer 3 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 and customers who purchase annual memberships are referred to as Cyclistic members. The company determined that annual members are more profitable than casual riders and is aiming to create a marketing campaign to maximize the number of annual members by converting casual riders.

Key stakeholder: Lily Moreno, the director of marketing responsible for developing campaigns to promote bike-share program.

Business task: How do annual members and casual riders use Cyclistic bikes differently?

Prepare Data

Since Cyclistic is a fictional company, we want to use proxy data from a similar bike-share company which can be downloaded from : https://divvy-tripdata.s3.amazonaws.com/index.html

After downloading 12 ZIP files, each containing a CSV file with data for each month between June 2021 and May 2022, install and load required packages, then import and bind all 12 data sets into a single data frame. read_csv will automatically extract ZIP files.

#Install and load required packages.
library(tidyverse) 
── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ──
✔ ggplot2 3.3.6     ✔ purrr   0.3.4
✔ tibble  3.1.7     ✔ dplyr   1.0.9
✔ tidyr   1.2.0     ✔ stringr 1.4.0
✔ readr   2.1.2     ✔ forcats 0.5.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(lubridate) 

Attaching package: 'lubridate'
The following objects are masked from 'package:base':

    date, intersect, setdiff, union
library(janitor)

Attaching package: 'janitor'
The following objects are masked from 'package:stats':

    chisq.test, fisher.test
library(rcartocolor)

# Suppress summarise additional informative output.
options(dplyr.summarise.inform = FALSE)
#Import and bind 12 datasets into single data frame. 
bikedata1 <- list.files("data", full.names = TRUE, pattern = "*.csv") %>% 
  lapply(read_csv) %>% 
  bind_rows()
Rows: 729595 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

ℹ 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.
Rows: 822410 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

ℹ 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.
Rows: 804352 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

ℹ 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.
Rows: 756147 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

ℹ 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.
Rows: 631226 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

ℹ 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.
Rows: 359978 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

ℹ 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.
Rows: 247540 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

ℹ 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.
Rows: 103770 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

ℹ 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.
Rows: 115609 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

ℹ 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.
Rows: 284042 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

ℹ 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.
Rows: 371249 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

ℹ 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.
Rows: 634858 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): ride_id, rideable_type, start_station_name, start_station_id, end_...
dbl  (4): start_lat, start_lng, end_lat, end_lng
dttm (2): started_at, ended_at

ℹ 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.

Data Information

#Preview data for immediate insights
str(bikedata1)
spec_tbl_df [5,860,776 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ ride_id           : chr [1:5860776] "99FEC93BA843FB20" "06048DCFC8520CAF" "9598066F68045DF2" "B03C0FE48C412214" ...
 $ rideable_type     : chr [1:5860776] "electric_bike" "electric_bike" "electric_bike" "electric_bike" ...
 $ started_at        : POSIXct[1:5860776], format: "2021-06-13 14:31:28" "2021-06-04 11:18:02" ...
 $ ended_at          : POSIXct[1:5860776], format: "2021-06-13 14:34:11" "2021-06-04 11:24:19" ...
 $ start_station_name: chr [1:5860776] NA NA NA NA ...
 $ start_station_id  : chr [1:5860776] NA NA NA NA ...
 $ end_station_name  : chr [1:5860776] NA NA NA NA ...
 $ end_station_id    : chr [1:5860776] NA NA NA NA ...
 $ start_lat         : num [1:5860776] 41.8 41.8 41.8 41.8 41.8 ...
 $ start_lng         : num [1:5860776] -87.6 -87.6 -87.6 -87.6 -87.6 ...
 $ end_lat           : num [1:5860776] 41.8 41.8 41.8 41.8 41.8 ...
 $ end_lng           : num [1:5860776] -87.6 -87.6 -87.6 -87.6 -87.6 ...
 $ member_casual     : chr [1:5860776] "member" "member" "member" "member" ...
 - attr(*, "spec")=
  .. cols(
  ..   ride_id = col_character(),
  ..   rideable_type = col_character(),
  ..   started_at = col_datetime(format = ""),
  ..   ended_at = col_datetime(format = ""),
  ..   start_station_name = col_character(),
  ..   start_station_id = col_character(),
  ..   end_station_name = col_character(),
  ..   end_station_id = col_character(),
  ..   start_lat = col_double(),
  ..   start_lng = col_double(),
  ..   end_lat = col_double(),
  ..   end_lng = col_double(),
  ..   member_casual = col_character()
  .. )
 - attr(*, "problems")=<externalptr> 
head(bikedata1)
glimpse(bikedata1)
Rows: 5,860,776
Columns: 13
$ ride_id            <chr> "99FEC93BA843FB20", "06048DCFC8520CAF", "9598066F68…
$ rideable_type      <chr> "electric_bike", "electric_bike", "electric_bike", …
$ started_at         <dttm> 2021-06-13 14:31:28, 2021-06-04 11:18:02, 2021-06-…
$ ended_at           <dttm> 2021-06-13 14:34:11, 2021-06-04 11:24:19, 2021-06-…
$ start_station_name <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ start_station_id   <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ end_station_name   <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, "Michigan Ave &…
$ end_station_id     <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, "13042", NA, NA…
$ start_lat          <dbl> 41.80, 41.79, 41.80, 41.78, 41.80, 41.78, 41.79, 41…
$ start_lng          <dbl> -87.59, -87.59, -87.60, -87.58, -87.59, -87.58, -87…
$ end_lat            <dbl> 41.80000, 41.80000, 41.79000, 41.80000, 41.79000, 4…
$ end_lng            <dbl> -87.6000, -87.6000, -87.5900, -87.6000, -87.5900, -…
$ member_casual      <chr> "member", "member", "member", "member", "member", "…
colnames(bikedata1)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
ride_id

rideable_type

started_at

ended_at

start_station_name

start_station_id

end_station_name

end_station_id

start_lat

start_lng

end_lat

end_lng

member_casual

Process and Clean Data for Analysis

We want to separate the date and add individual columns for the month, day, year, and day of the week. This will allow us to aggregate data and perform calculations easier. We also want to find the length of each ride using the difftime() function, then convert it to a format that is easy to read (HH:MM:SS).

bikedata1$date <- as_date(bikedata1$started_at)

#Add column for month
bikedata1$month <- format(as_date(bikedata1$date), "%m")

#Add column for day
bikedata1$day <- format(as_date(bikedata1$date), "%d")

#Add column for year
bikedata1$year <- format(as_date(bikedata1$date), "%Y")

#Add Column for day of the week
bikedata1$day_of_week <- format(as_date(bikedata1$started_at), "%A")
#Add column for ride length
bikedata1$ride_length <- difftime(bikedata1$ended_at, bikedata1$started_at) %>% as.numeric()

We want to remove columns start_lat, start_lng, end_lat, and end_lng as the data was dropped starting in 2020. We also need to decide how to handle missing values which were primarily in start_station_name, start_station_id, end_station_name, and end_station_id. We could try to find the missing stations using the latitude/longitude but the numbers are not accurate to definitively pinpoint the correct station. That leaves us with either removing the variable (column) or observation (row). I decided to remove the rows with missing values to minimize inaccurate data because the missing values may have influenced the start/end times. For example, a bike could have been docked incorrectly and the time continued to run.

#Remove lat/lng columns
bikedata1 <- bikedata1 %>% select(-start_lat, -start_lng, -end_lat, -end_lng)

#Remove rows with missing values
bikedata1 <- na.omit(bikedata1)

We want to verify the data and look for any inconsistent or inaccurate data. We confirmed that there are no duplicates, each ride is either member or casual, and the data is within the correct date range. However, there were 3 ride types (electric_bike, classic_bike, and docked_bike) which was not expected. After reviewing the types of bikes available on the Divvy Bike website, there there is no mention of a docked bike. I decided to remove rows with docked bikes to minimize the risk of inaccurate date.

We also found rows with ride_length less than or equal to 0 which is not accurate and therefore removed.

#Check for duplicates
bikedata1 %>% get_dupes(ride_id)
No duplicate combinations found of: ride_id
#Check ride type
bikedata1 %>% count(rideable_type)
#Check member type
bikedata1 %>% count(member_casual)
#Check for data outside date range
bikedata1 %>% filter(started_at <= as_date("2021-05-31") | started_at >= as_date("2022-06-01"))
#Check for ride length less than or equal to 0
bikedata1 %>% select(ride_length) %>% filter(ride_length <= 0)
#Remove rows with docked bike
bikedata1 <- bikedata1 %>% filter(!rideable_type == "docked_bike")

#Remove rows with less than or equal to 0 ride lengths
bikedata1 <- bikedata1 %>% filter(!ride_length <= 0)

Analyze Data

summary(as.numeric(bikedata1$ride_length))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1     383     665    1020    1173   89994 
aggregate(list(ride_length_mean = bikedata1$ride_length), list(member_type = bikedata1$member_casual), mean)
aggregate(list(ride_length_median = bikedata1$ride_length), list(member_type = bikedata1$member_casual), median)
aggregate(list(ride_length_max = bikedata1$ride_length), list(member_type = bikedata1$member_casual), max)
aggregate(list(ride_length_min = bikedata1$ride_length), list(member_type = bikedata1$member_casual), min)
#Order week with Sunday as first day of the week 
bikedata1$day_of_week <- ordered(bikedata1$day_of_week, levels = c('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'))

#Aggregate data to average ride duration vs. member type/day of week
aggregate(list(ride_length_mean = bikedata1$ride_length), list(member_type = bikedata1$member_casual, day_of_week = bikedata1$day_of_week), mean)
#Plot average ride duration vs. day of week for members and casual riders.
bikedata1 %>% 
  group_by(day_of_week, member_casual) %>% 
  arrange(day_of_week) %>% 
  summarise(rides = n(), average_duration = mean(ride_length)) %>% 
  ggplot(aes(x = day_of_week, y = average_duration, fill = member_casual)) + geom_col(position = "dodge") + scale_fill_carto_d(palette = "Vivid") + labs(x = "Day of Week", y = "Average Ride Duration (Seconds)", title = "Average Ride Duration vs. Day of Week for Members and Casual Riders") + theme(plot.title = element_text(hjust = 0.5), legend.title = element_blank())

#Plot number of rides vs. day of week for members and casual riders.
bikedata1 %>% 
  group_by(day_of_week, member_casual) %>% 
  arrange(day_of_week) %>% 
  summarise(rides = n(), average_duration = mean(ride_length)) %>% 
  ggplot(aes(x = day_of_week, y = rides, fill = member_casual)) + geom_col(position = "dodge") + scale_fill_carto_d(palette = "Vivid") + labs(x = "Day of Week", y = "Number of Rides", title = "Number of Rides vs. Day of Week for Members and Casual Riders") + theme(plot.title = element_text(hjust = 0.5), legend.title = element_blank())

#Plot average ride duration vs. month for members and casual riders
bikedata1 %>% 
  mutate(month = format(as_date(started_at), "%b") %>% factor(levels = month.abb)) %>% 
  group_by(member_casual, month) %>% 
  arrange(month) %>% 
  summarise(rides = n(), average_duration = mean(ride_length)) %>% 
  ggplot(aes(x = month, y = average_duration, fill = member_casual)) + geom_col(position = "dodge") + scale_fill_carto_d(palette = "Vivid") + labs(x = "Month", y = "Average Ride Duration (Seconds)", title = "Average Ride Duration vs. Month for Members and Casual Riders") + theme(plot.title = element_text(hjust = 0.5), legend.title = element_blank())

#Plot number of rides vs. month for members and casual riders
bikedata1 %>% 
  mutate(month = format(as_date(started_at), "%b") %>% factor(levels = month.abb)) %>% 
  group_by(member_casual, month) %>% 
  arrange(month) %>% 
  summarise(rides = n(), average_duration = mean(ride_length)) %>% 
  ggplot(aes(x = month, y = rides, fill = member_casual)) + geom_col(position = "dodge") + scale_fill_carto_d(palette = "Vivid") + labs(x = "Month", y = "Number of Rides", title = "Number of Rides vs. Month for Members and Casual Riders") + theme(plot.title = element_text(hjust = 0.5), legend.title = element_blank())

Share Data

Act

LS0tDQp0aXRsZTogIkJpa2UgU2hhcmUgQ2FzZSBTdHVkeSINCmF1dGhvcjogIkFuZHkgR3VvIg0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0OiAiaHRtbF9ub3RlYm9vayINCi0tLQ0KDQpGb3IgbXkgR29vZ2xlIERhdGEgQW5hbHl0aWNzIGNhcHN0b25lIHByb2plY3QsIEkgY29tcGxldGVkIGEgY2FzZSBzdHVkeSBmb3IgYSBmaWN0aW9uYWwgYmlrZS1zaGFyZSBjb21wYW55IGluIHdoaWNoIEkgYW5hbHl6ZWQgMTIgbW9udGhzIG9mIGRhdGEgdG8gdW5kZXJzdGFuZCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGNhc3VhbCByaWRlcnMgYW5kIGFubnVhbCBtZW1iZXJzLiBGcm9tIHRoZXNlIGluc2lnaHRzLCB0aGUgbWFya2V0aW5nIHRlYW0gY2FuIGRldmVsb3AgYSBzdHJhdGVneSB0byBjb252ZXJ0IGNhc3VhbCByaWRlcnMgaW50byBhbm51YWwgbWVtYmVycy4gVG8gYWNjb21wbGlzaCBteSB0YXNrLCBJIGFwcGxpZWQgdGhlIGRhdGEgYW5hbHlzaXMgcHJvY2VzcyBpbmNsdWRpbmcgYXNrLCBwcmVwYXJlLCBwcm9jZXNzLCBhbmFseXplLCBzaGFyZSwgYW5kIGFjdC4NCg0KIyMjIyBBc2sgdGhlIFJpZ2h0IFF1ZXN0aW9ucw0KDQpDeWNsaXN0aWMgaXMgYSBiaWtlLXNoYXJlIGNvbXBhbnkgaW4gQ2hpY2FnbyB3aXRoIGEgZmxlZXQgb2YgNSw4MjQgZ2VvdHJhY2tlZCBiaWN5Y2xlcyBhbmQgYSBuZXR3b3JrIG9mIDY5MiBzdGF0aW9ucy4gVGhleSBvZmZlciAzIHByaWNpbmcgcGxhbnM6IHNpbmdsZS1yaWRlIHBhc3NlcywgZnVsbC1kYXkgcGFzc2VzLCBhbmQgYW5udWFsIG1lbWJlcnNoaXBzLiBDdXN0b21lcnMgd2hvIHB1cmNoYXNlIHNpbmdsZS1yaWRlIG9yIGZ1bGwtZGF5IHBhc3NlcyBhcmUgcmVmZXJyZWQgdG8gYXMgY2FzdWFsIHJpZGVycyBhbmQgY3VzdG9tZXJzIHdobyBwdXJjaGFzZSBhbm51YWwgbWVtYmVyc2hpcHMgYXJlIHJlZmVycmVkIHRvIGFzIEN5Y2xpc3RpYyBtZW1iZXJzLiBUaGUgY29tcGFueSBkZXRlcm1pbmVkIHRoYXQgYW5udWFsIG1lbWJlcnMgYXJlIG1vcmUgcHJvZml0YWJsZSB0aGFuIGNhc3VhbCByaWRlcnMgYW5kIGlzIGFpbWluZyB0byBjcmVhdGUgYSBtYXJrZXRpbmcgY2FtcGFpZ24gdG8gbWF4aW1pemUgdGhlIG51bWJlciBvZiBhbm51YWwgbWVtYmVycyBieSBjb252ZXJ0aW5nIGNhc3VhbCByaWRlcnMuDQoNCioqS2V5IHN0YWtlaG9sZGVyKio6IExpbHkgTW9yZW5vLCB0aGUgZGlyZWN0b3Igb2YgbWFya2V0aW5nIHJlc3BvbnNpYmxlIGZvciBkZXZlbG9waW5nIGNhbXBhaWducyB0byBwcm9tb3RlIGJpa2Utc2hhcmUgcHJvZ3JhbS4NCg0KKipCdXNpbmVzcyB0YXNrOioqIEhvdyBkbyBhbm51YWwgbWVtYmVycyBhbmQgY2FzdWFsIHJpZGVycyB1c2UgQ3ljbGlzdGljIGJpa2VzIGRpZmZlcmVudGx5Pw0KDQojIyMjIFByZXBhcmUgRGF0YQ0KDQpTaW5jZSBDeWNsaXN0aWMgaXMgYSBmaWN0aW9uYWwgY29tcGFueSwgd2Ugd2FudCB0byB1c2UgcHJveHkgZGF0YSBmcm9tIGEgc2ltaWxhciBiaWtlLXNoYXJlIGNvbXBhbnkgd2hpY2ggY2FuIGJlIGRvd25sb2FkZWQgZnJvbSA6IDxodHRwczovL2RpdnZ5LXRyaXBkYXRhLnMzLmFtYXpvbmF3cy5jb20vaW5kZXguaHRtbD4NCg0KQWZ0ZXIgZG93bmxvYWRpbmcgMTIgWklQIGZpbGVzLCBlYWNoIGNvbnRhaW5pbmcgYSBDU1YgZmlsZSB3aXRoIGRhdGEgZm9yIGVhY2ggbW9udGggYmV0d2VlbiBKdW5lIDIwMjEgYW5kIE1heSAyMDIyLCBpbnN0YWxsIGFuZCBsb2FkIHJlcXVpcmVkIHBhY2thZ2VzLCB0aGVuIGltcG9ydCBhbmQgYmluZCBhbGwgMTIgZGF0YSBzZXRzIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZS4gYHJlYWRfY3N2YCB3aWxsIGF1dG9tYXRpY2FsbHkgZXh0cmFjdCBaSVAgZmlsZXMuDQoNCmBgYHtyIGxvYWRfcGFja2FnZXN9DQojSW5zdGFsbCBhbmQgbG9hZCByZXF1aXJlZCBwYWNrYWdlcy4NCmxpYnJhcnkodGlkeXZlcnNlKSANCmxpYnJhcnkobHVicmlkYXRlKSANCmxpYnJhcnkoamFuaXRvcikNCmxpYnJhcnkocmNhcnRvY29sb3IpDQoNCiMgU3VwcHJlc3Mgc3VtbWFyaXNlIGFkZGl0aW9uYWwgaW5mb3JtYXRpdmUgb3V0cHV0Lg0Kb3B0aW9ucyhkcGx5ci5zdW1tYXJpc2UuaW5mb3JtID0gRkFMU0UpDQpgYGANCg0KYGBge3IgaW1wb3J0X2FuZF9iaW5kfQ0KI0ltcG9ydCBhbmQgYmluZCAxMiBkYXRhc2V0cyBpbnRvIHNpbmdsZSBkYXRhIGZyYW1lLiANCmJpa2VkYXRhMSA8LSBsaXN0LmZpbGVzKCJkYXRhIiwgZnVsbC5uYW1lcyA9IFRSVUUsIHBhdHRlcm4gPSAiKi5jc3YiKSAlPiUgDQogIGxhcHBseShyZWFkX2NzdikgJT4lIA0KICBiaW5kX3Jvd3MoKQ0KYGBgDQoNCkRhdGEgSW5mb3JtYXRpb24NCg0KLSAgIFRoZXJlIGFyZSAxMyBjb2x1bW5zIGFuZCA1ODYwNzc2IHJvd3MuDQoNCi0gICBEYXRhIHR5cGVzIGluY2x1ZGUgMiBkYXRldGltZSwgNCBkb3VibGUsIGFuZCA3IGNoYXJhY3Rlci4NCg0KLSAgIFRoZXJlIGFyZSBtaXNzaW5nIHZhbHVlcyBpbiBzdGFydF9zdGF0aW9uX25hbWUsIHN0YXJ0X3N0YXRpb25faWQsIGVuZF9zdGF0aW9uX25hbWUsIGVuZF9zdGF0aW9uX2lkDQoNCmBgYHtyIHByZXZpZXdfZGF0YX0NCiNQcmV2aWV3IGRhdGEgZm9yIGltbWVkaWF0ZSBpbnNpZ2h0cw0Kc3RyKGJpa2VkYXRhMSkNCmhlYWQoYmlrZWRhdGExKQ0KZ2xpbXBzZShiaWtlZGF0YTEpDQpjb2xuYW1lcyhiaWtlZGF0YTEpDQpgYGANCg0KIyMjIyBQcm9jZXNzIGFuZCBDbGVhbiBEYXRhIGZvciBBbmFseXNpcw0KDQpXZSB3YW50IHRvIHNlcGFyYXRlIHRoZSBkYXRlIGFuZCBhZGQgaW5kaXZpZHVhbCBjb2x1bW5zIGZvciB0aGUgbW9udGgsIGRheSwgeWVhciwgYW5kIGRheSBvZiB0aGUgd2Vlay4gVGhpcyB3aWxsIGFsbG93IHVzIHRvIGFnZ3JlZ2F0ZSBkYXRhIGFuZCBwZXJmb3JtIGNhbGN1bGF0aW9ucyBlYXNpZXIuIFdlIGFsc28gd2FudCB0byBmaW5kIHRoZSBsZW5ndGggb2YgZWFjaCByaWRlIHVzaW5nIHRoZSBgZGlmZnRpbWUoKWAgZnVuY3Rpb24sIHRoZW4gY29udmVydCBpdCB0byBhIGZvcm1hdCB0aGF0IGlzIGVhc3kgdG8gcmVhZCAoSEg6TU06U1MpLg0KDQpgYGB7ciBhZGRfZGF0ZV9jb2x1bW5zfQ0KYmlrZWRhdGExJGRhdGUgPC0gYXNfZGF0ZShiaWtlZGF0YTEkc3RhcnRlZF9hdCkNCg0KI0FkZCBjb2x1bW4gZm9yIG1vbnRoDQpiaWtlZGF0YTEkbW9udGggPC0gZm9ybWF0KGFzX2RhdGUoYmlrZWRhdGExJGRhdGUpLCAiJW0iKQ0KDQojQWRkIGNvbHVtbiBmb3IgZGF5DQpiaWtlZGF0YTEkZGF5IDwtIGZvcm1hdChhc19kYXRlKGJpa2VkYXRhMSRkYXRlKSwgIiVkIikNCg0KI0FkZCBjb2x1bW4gZm9yIHllYXINCmJpa2VkYXRhMSR5ZWFyIDwtIGZvcm1hdChhc19kYXRlKGJpa2VkYXRhMSRkYXRlKSwgIiVZIikNCg0KI0FkZCBDb2x1bW4gZm9yIGRheSBvZiB0aGUgd2Vlaw0KYmlrZWRhdGExJGRheV9vZl93ZWVrIDwtIGZvcm1hdChhc19kYXRlKGJpa2VkYXRhMSRzdGFydGVkX2F0KSwgIiVBIikNCmBgYA0KDQpgYGB7ciBhZGRfcmlkZV9sZW5ndGh9DQojQWRkIGNvbHVtbiBmb3IgcmlkZSBsZW5ndGgNCmJpa2VkYXRhMSRyaWRlX2xlbmd0aCA8LSBkaWZmdGltZShiaWtlZGF0YTEkZW5kZWRfYXQsIGJpa2VkYXRhMSRzdGFydGVkX2F0KSAlPiUgYXMubnVtZXJpYygpDQpgYGANCg0KV2Ugd2FudCB0byByZW1vdmUgY29sdW1ucyBgc3RhcnRfbGF0YCwgYHN0YXJ0X2xuZ2AsIGBlbmRfbGF0YCwgYW5kIGBlbmRfbG5nYCBhcyB0aGUgZGF0YSB3YXMgZHJvcHBlZCBzdGFydGluZyBpbiAyMDIwLiBXZSBhbHNvIG5lZWQgdG8gZGVjaWRlIGhvdyB0byBoYW5kbGUgbWlzc2luZyB2YWx1ZXMgd2hpY2ggd2VyZSBwcmltYXJpbHkgaW4gYHN0YXJ0X3N0YXRpb25fbmFtZWAsIGBzdGFydF9zdGF0aW9uX2lkYCwgYGVuZF9zdGF0aW9uX25hbWVgLCBhbmQgYGVuZF9zdGF0aW9uX2lkYC4gV2UgY291bGQgdHJ5IHRvIGZpbmQgdGhlIG1pc3Npbmcgc3RhdGlvbnMgdXNpbmcgdGhlIGxhdGl0dWRlL2xvbmdpdHVkZSBidXQgdGhlIG51bWJlcnMgYXJlIG5vdCBhY2N1cmF0ZSB0byBkZWZpbml0aXZlbHkgcGlucG9pbnQgdGhlIGNvcnJlY3Qgc3RhdGlvbi4gVGhhdCBsZWF2ZXMgdXMgd2l0aCBlaXRoZXIgcmVtb3ZpbmcgdGhlIHZhcmlhYmxlIChjb2x1bW4pIG9yIG9ic2VydmF0aW9uIChyb3cpLiBJIGRlY2lkZWQgdG8gcmVtb3ZlIHRoZSByb3dzIHdpdGggbWlzc2luZyB2YWx1ZXMgdG8gbWluaW1pemUgaW5hY2N1cmF0ZSBkYXRhIGJlY2F1c2UgdGhlIG1pc3NpbmcgdmFsdWVzIG1heSBoYXZlIGluZmx1ZW5jZWQgdGhlIHN0YXJ0L2VuZCB0aW1lcy4gRm9yIGV4YW1wbGUsIGEgYmlrZSBjb3VsZCBoYXZlIGJlZW4gZG9ja2VkIGluY29ycmVjdGx5IGFuZCB0aGUgdGltZSBjb250aW51ZWQgdG8gcnVuLg0KDQpgYGB7ciByZW1vdmVfaXJyZWxldmFudF9kYXRhfQ0KI1JlbW92ZSBsYXQvbG5nIGNvbHVtbnMNCmJpa2VkYXRhMSA8LSBiaWtlZGF0YTEgJT4lIHNlbGVjdCgtc3RhcnRfbGF0LCAtc3RhcnRfbG5nLCAtZW5kX2xhdCwgLWVuZF9sbmcpDQoNCiNSZW1vdmUgcm93cyB3aXRoIG1pc3NpbmcgdmFsdWVzDQpiaWtlZGF0YTEgPC0gbmEub21pdChiaWtlZGF0YTEpDQpgYGANCg0KV2Ugd2FudCB0byB2ZXJpZnkgdGhlIGRhdGEgYW5kIGxvb2sgZm9yIGFueSBpbmNvbnNpc3RlbnQgb3IgaW5hY2N1cmF0ZSBkYXRhLiBXZSBjb25maXJtZWQgdGhhdCB0aGVyZSBhcmUgbm8gZHVwbGljYXRlcywgZWFjaCByaWRlIGlzIGVpdGhlciBtZW1iZXIgb3IgY2FzdWFsLCBhbmQgdGhlIGRhdGEgaXMgd2l0aGluIHRoZSBjb3JyZWN0IGRhdGUgcmFuZ2UuIEhvd2V2ZXIsIHRoZXJlIHdlcmUgMyByaWRlIHR5cGVzIChlbGVjdHJpY19iaWtlLCBjbGFzc2ljX2Jpa2UsIGFuZCBkb2NrZWRfYmlrZSkgd2hpY2ggd2FzIG5vdCBleHBlY3RlZC4gQWZ0ZXIgcmV2aWV3aW5nIHRoZSB0eXBlcyBvZiBiaWtlcyBhdmFpbGFibGUgb24gdGhlIFtEaXZ2eSBCaWtlIHdlYnNpdGVdKGh0dHBzOi8vZGl2dnliaWtlcy5jb20vaG93LWl0LXdvcmtzL21lZXQtdGhlLWJpa2VzKSwgdGhlcmUgdGhlcmUgaXMgbm8gbWVudGlvbiBvZiBhIGRvY2tlZCBiaWtlLiBJIGRlY2lkZWQgdG8gcmVtb3ZlIHJvd3Mgd2l0aCBkb2NrZWQgYmlrZXMgdG8gbWluaW1pemUgdGhlIHJpc2sgb2YgaW5hY2N1cmF0ZSBkYXRlLg0KDQpXZSBhbHNvIGZvdW5kIHJvd3Mgd2l0aCByaWRlX2xlbmd0aCBsZXNzIHRoYW4gb3IgZXF1YWwgdG8gMCB3aGljaCBpcyBub3QgYWNjdXJhdGUgYW5kIHRoZXJlZm9yZSByZW1vdmVkLg0KDQpgYGB7ciB2ZXJpZnlfZGF0YX0NCiNDaGVjayBmb3IgZHVwbGljYXRlcw0KYmlrZWRhdGExICU+JSBnZXRfZHVwZXMocmlkZV9pZCkNCg0KI0NoZWNrIHJpZGUgdHlwZQ0KYmlrZWRhdGExICU+JSBjb3VudChyaWRlYWJsZV90eXBlKQ0KDQojQ2hlY2sgbWVtYmVyIHR5cGUNCmJpa2VkYXRhMSAlPiUgY291bnQobWVtYmVyX2Nhc3VhbCkNCg0KI0NoZWNrIGZvciBkYXRhIG91dHNpZGUgZGF0ZSByYW5nZQ0KYmlrZWRhdGExICU+JSBmaWx0ZXIoc3RhcnRlZF9hdCA8PSBhc19kYXRlKCIyMDIxLTA1LTMxIikgfCBzdGFydGVkX2F0ID49IGFzX2RhdGUoIjIwMjItMDYtMDEiKSkNCg0KI0NoZWNrIGZvciByaWRlIGxlbmd0aCBsZXNzIHRoYW4gb3IgZXF1YWwgdG8gMA0KYmlrZWRhdGExICU+JSBzZWxlY3QocmlkZV9sZW5ndGgpICU+JSBmaWx0ZXIocmlkZV9sZW5ndGggPD0gMCkNCmBgYA0KDQpgYGB7ciByZW1vdmVfZGlydHlfZGF0YX0NCiNSZW1vdmUgcm93cyB3aXRoIGRvY2tlZCBiaWtlDQpiaWtlZGF0YTEgPC0gYmlrZWRhdGExICU+JSBmaWx0ZXIoIXJpZGVhYmxlX3R5cGUgPT0gImRvY2tlZF9iaWtlIikNCg0KI1JlbW92ZSByb3dzIHdpdGggbGVzcyB0aGFuIG9yIGVxdWFsIHRvIDAgcmlkZSBsZW5ndGhzDQpiaWtlZGF0YTEgPC0gYmlrZWRhdGExICU+JSBmaWx0ZXIoIXJpZGVfbGVuZ3RoIDw9IDApDQpgYGANCg0KIyMjIyBBbmFseXplIERhdGENCg0KYGBge3Igc3VtbWFyeV9yaWRlX2xlbmd0aH0NCnN1bW1hcnkoYXMubnVtZXJpYyhiaWtlZGF0YTEkcmlkZV9sZW5ndGgpKQ0KYGBgDQoNCmBgYHtyIGFnZ3JlZ2F0ZV9kYXRhfQ0KYWdncmVnYXRlKGxpc3QocmlkZV9sZW5ndGhfbWVhbiA9IGJpa2VkYXRhMSRyaWRlX2xlbmd0aCksIGxpc3QobWVtYmVyX3R5cGUgPSBiaWtlZGF0YTEkbWVtYmVyX2Nhc3VhbCksIG1lYW4pDQoNCmFnZ3JlZ2F0ZShsaXN0KHJpZGVfbGVuZ3RoX21lZGlhbiA9IGJpa2VkYXRhMSRyaWRlX2xlbmd0aCksIGxpc3QobWVtYmVyX3R5cGUgPSBiaWtlZGF0YTEkbWVtYmVyX2Nhc3VhbCksIG1lZGlhbikNCg0KYWdncmVnYXRlKGxpc3QocmlkZV9sZW5ndGhfbWF4ID0gYmlrZWRhdGExJHJpZGVfbGVuZ3RoKSwgbGlzdChtZW1iZXJfdHlwZSA9IGJpa2VkYXRhMSRtZW1iZXJfY2FzdWFsKSwgbWF4KQ0KDQphZ2dyZWdhdGUobGlzdChyaWRlX2xlbmd0aF9taW4gPSBiaWtlZGF0YTEkcmlkZV9sZW5ndGgpLCBsaXN0KG1lbWJlcl90eXBlID0gYmlrZWRhdGExJG1lbWJlcl9jYXN1YWwpLCBtaW4pDQpgYGANCg0KYGBge3IgYWdncmVnYXRlX29yZGVyX2RhdGF9DQojT3JkZXIgd2VlayB3aXRoIFN1bmRheSBhcyBmaXJzdCBkYXkgb2YgdGhlIHdlZWsgDQpiaWtlZGF0YTEkZGF5X29mX3dlZWsgPC0gb3JkZXJlZChiaWtlZGF0YTEkZGF5X29mX3dlZWssIGxldmVscyA9IGMoJ1N1bmRheScsICdNb25kYXknLCAnVHVlc2RheScsICdXZWRuZXNkYXknLCAnVGh1cnNkYXknLCAnRnJpZGF5JywgJ1NhdHVyZGF5JykpDQoNCiNBZ2dyZWdhdGUgZGF0YSB0byBhdmVyYWdlIHJpZGUgZHVyYXRpb24gdnMuIG1lbWJlciB0eXBlL2RheSBvZiB3ZWVrDQphZ2dyZWdhdGUobGlzdChyaWRlX2xlbmd0aF9tZWFuID0gYmlrZWRhdGExJHJpZGVfbGVuZ3RoKSwgbGlzdChtZW1iZXJfdHlwZSA9IGJpa2VkYXRhMSRtZW1iZXJfY2FzdWFsLCBkYXlfb2Zfd2VlayA9IGJpa2VkYXRhMSRkYXlfb2Zfd2VlayksIG1lYW4pDQoNCmBgYA0KDQpgYGB7ciBwbG90X2RheV9vZl93ZWVrfQ0KI1Bsb3QgYXZlcmFnZSByaWRlIGR1cmF0aW9uIHZzLiBkYXkgb2Ygd2VlayBmb3IgbWVtYmVycyBhbmQgY2FzdWFsIHJpZGVycy4NCmJpa2VkYXRhMSAlPiUgDQogIGdyb3VwX2J5KGRheV9vZl93ZWVrLCBtZW1iZXJfY2FzdWFsKSAlPiUgDQogIGFycmFuZ2UoZGF5X29mX3dlZWspICU+JSANCiAgc3VtbWFyaXNlKHJpZGVzID0gbigpLCBhdmVyYWdlX2R1cmF0aW9uID0gbWVhbihyaWRlX2xlbmd0aCkpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gZGF5X29mX3dlZWssIHkgPSBhdmVyYWdlX2R1cmF0aW9uLCBmaWxsID0gbWVtYmVyX2Nhc3VhbCkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSArIHNjYWxlX2ZpbGxfY2FydG9fZChwYWxldHRlID0gIlZpdmlkIikgKyBsYWJzKHggPSAiRGF5IG9mIFdlZWsiLCB5ID0gIkF2ZXJhZ2UgUmlkZSBEdXJhdGlvbiAoU2Vjb25kcykiLCB0aXRsZSA9ICJBdmVyYWdlIFJpZGUgRHVyYXRpb24gdnMuIERheSBvZiBXZWVrIGZvciBNZW1iZXJzIGFuZCBDYXN1YWwgUmlkZXJzIikgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQ0KDQojUGxvdCBudW1iZXIgb2YgcmlkZXMgdnMuIGRheSBvZiB3ZWVrIGZvciBtZW1iZXJzIGFuZCBjYXN1YWwgcmlkZXJzLg0KYmlrZWRhdGExICU+JSANCiAgZ3JvdXBfYnkoZGF5X29mX3dlZWssIG1lbWJlcl9jYXN1YWwpICU+JSANCiAgYXJyYW5nZShkYXlfb2Zfd2VlaykgJT4lIA0KICBzdW1tYXJpc2UocmlkZXMgPSBuKCksIGF2ZXJhZ2VfZHVyYXRpb24gPSBtZWFuKHJpZGVfbGVuZ3RoKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBkYXlfb2Zfd2VlaywgeSA9IHJpZGVzLCBmaWxsID0gbWVtYmVyX2Nhc3VhbCkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSArIHNjYWxlX2ZpbGxfY2FydG9fZChwYWxldHRlID0gIlZpdmlkIikgKyBsYWJzKHggPSAiRGF5IG9mIFdlZWsiLCB5ID0gIk51bWJlciBvZiBSaWRlcyIsIHRpdGxlID0gIk51bWJlciBvZiBSaWRlcyB2cy4gRGF5IG9mIFdlZWsgZm9yIE1lbWJlcnMgYW5kIENhc3VhbCBSaWRlcnMiKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLCBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpDQpgYGANCg0KYGBge3IgcGxvdF9tb250aH0NCiNQbG90IGF2ZXJhZ2UgcmlkZSBkdXJhdGlvbiB2cy4gbW9udGggZm9yIG1lbWJlcnMgYW5kIGNhc3VhbCByaWRlcnMNCmJpa2VkYXRhMSAlPiUgDQogIG11dGF0ZShtb250aCA9IGZvcm1hdChhc19kYXRlKHN0YXJ0ZWRfYXQpLCAiJWIiKSAlPiUgZmFjdG9yKGxldmVscyA9IG1vbnRoLmFiYikpICU+JSANCiAgZ3JvdXBfYnkobWVtYmVyX2Nhc3VhbCwgbW9udGgpICU+JSANCiAgYXJyYW5nZShtb250aCkgJT4lIA0KICBzdW1tYXJpc2UocmlkZXMgPSBuKCksIGF2ZXJhZ2VfZHVyYXRpb24gPSBtZWFuKHJpZGVfbGVuZ3RoKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBtb250aCwgeSA9IGF2ZXJhZ2VfZHVyYXRpb24sIGZpbGwgPSBtZW1iZXJfY2FzdWFsKSkgKyBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpICsgc2NhbGVfZmlsbF9jYXJ0b19kKHBhbGV0dGUgPSAiVml2aWQiKSArIGxhYnMoeCA9ICJNb250aCIsIHkgPSAiQXZlcmFnZSBSaWRlIER1cmF0aW9uIChTZWNvbmRzKSIsIHRpdGxlID0gIkF2ZXJhZ2UgUmlkZSBEdXJhdGlvbiB2cy4gTW9udGggZm9yIE1lbWJlcnMgYW5kIENhc3VhbCBSaWRlcnMiKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLCBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpDQoNCiNQbG90IG51bWJlciBvZiByaWRlcyB2cy4gbW9udGggZm9yIG1lbWJlcnMgYW5kIGNhc3VhbCByaWRlcnMNCmJpa2VkYXRhMSAlPiUgDQogIG11dGF0ZShtb250aCA9IGZvcm1hdChhc19kYXRlKHN0YXJ0ZWRfYXQpLCAiJWIiKSAlPiUgZmFjdG9yKGxldmVscyA9IG1vbnRoLmFiYikpICU+JSANCiAgZ3JvdXBfYnkobWVtYmVyX2Nhc3VhbCwgbW9udGgpICU+JSANCiAgYXJyYW5nZShtb250aCkgJT4lIA0KICBzdW1tYXJpc2UocmlkZXMgPSBuKCksIGF2ZXJhZ2VfZHVyYXRpb24gPSBtZWFuKHJpZGVfbGVuZ3RoKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBtb250aCwgeSA9IHJpZGVzLCBmaWxsID0gbWVtYmVyX2Nhc3VhbCkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSArIHNjYWxlX2ZpbGxfY2FydG9fZChwYWxldHRlID0gIlZpdmlkIikgKyBsYWJzKHggPSAiTW9udGgiLCB5ID0gIk51bWJlciBvZiBSaWRlcyIsIHRpdGxlID0gIk51bWJlciBvZiBSaWRlcyB2cy4gTW9udGggZm9yIE1lbWJlcnMgYW5kIENhc3VhbCBSaWRlcnMiKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLCBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpDQpgYGANCg0KU2hhcmUgRGF0YQ0KDQpBY3QNCg==