Goal
Create a post with similar parameters to the Minnesota DOT’s Covid-19 & Historic Total Traffic Count graph.
Connect to MySQL Database
Load the table into R
counts_daily <- tbl(con, 'counts_daily')
head(counts_daily)
Combining ‘In’ and ‘Out’ Values
Below is the code we created in In Part 5, to combine the ‘I’ and ‘O’ values.
counts_daily_total <- counts_daily %>%
group_by(date, bikeometer_id) %>%
summarize(count = sum(count), is_weekend = is_weekend,
month = month, day = day, year = year, month_day = month_day) %>%
collect()
## `summarise()` has grouped output by 'date'. You can override using the `.groups` argument.
class(counts_daily_total$month_day)
## [1] "character"
I’m going to manipulate the counts_daily_total ‘tibble’ using the mutate() function.
I realized that I should have used a hyphen instead of an underscore for the ‘month-day’ values so first I need to do some character replacement. I’ll use the mutate() function to make changes to columns and pass in the gsub() function to replace the underscore with a hyphen.
Then, we will add an arbitrary year to the end of month_day to make it easier to convert to a date class and subsequently graph.
counts_daily_total <- counts_daily_total %>% mutate(month_day = gsub("[_]", "-", month_day))
counts_daily_total <- counts_daily_total %>% mutate(month_day=paste(month_day, '-2020', sep = ''))
counts_daily_total
Now, let’s convert month_day to a date class.
class(counts_daily_total$month_day[1])
## [1] "character"
counts_daily_total$month_day <- strptime(counts_daily_total$month_day, '%m-%d-%Y')
class(counts_daily_total$month_day[1])
## [1] "POSIXlt" "POSIXt"
counts_daily_total$month_day <- as.Date(counts_daily_total$month_day)
class(counts_daily_total$month_day[1])
## [1] "Date"
You can see that the column goes from classes ‘character’ to ‘POSIXlt POSIXt’ to finally ‘Date’.
Note to self
Originally, class(counts_daily_total$year,month,day == ‘S3 value Bigint’ which doesn’t play nicely with dplyr. Be sure to convert number columns into int before applying filters. Also, when mutating a column, don’t assign the mutate like this:
# Don't use this
counts_daily_total$month_day <- counts_daily_total %>% mutate(month_day = gsub("[_]", "-", month_day))
# Use this!
counts_daily_total <- counts_daily_total %>% mutate(month_day = gsub("[_]", "-", month_day))
Filtering for Date Range and Bikeometer ID
In a previous post, I chose a time-frame and which Bikeometers were best to plot.
counts_daily_filtered_2017_to_2019 <- counts_daily_total %>%
filter(date >= '2017-03-12' & date <= '2017-05-15' & bikeometer_id %in% c(14,15,16,18,22,31,39)|
date >= '2018-03-12' & date <= '2018-05-15' & bikeometer_id %in% c(14,15,16,18,22,31,39)|
date >= '2019-03-12' & date <= '2019-05-15' & bikeometer_id %in% c(14,15,16,18,22,31,39))
counts_daily_filtered_2017_to_2019
counts_daily_filtered_2020 <- counts_daily_total %>%
filter(date >= '2020-03-12' & date <= '2020-05-15' & bikeometer_id %in% c(14,15,16,18,22,31,39))
counts_daily_filtered_2020
Group the 2020 Bikeometer counts
Now we can can group the counts by taking their average. I’ll rename the column ‘2020_counts’ to make it easier to merge the table later.
counts_daily_filtered_2020_grouped <- counts_daily_filtered_2020 %>% group_by(month_day) %>% summarise('2020_counts' = mean(count))
counts_daily_filtered_2020_grouped
Creating the 2017-2019 Daily Average column
Now that we have two tables, one with 2017-2019 data and one with 2020 data, let’s add a column to the counts_daily_filtered_2017_to_2019 table that shows the average across all Bikeometers.
counts_daily_filtered_2017_to_2019_grouped <- counts_daily_filtered_2017_to_2019 %>% group_by(month_day) %>% summarise('2017-2019_avg_counts' = mean(count))
counts_daily_filtered_2017_to_2019_grouped
We should check to see if the average is being taken correctly. We can manually check the first ‘month_day’ below.
counts_daily_filtered_2017_to_2019 %>% select(month_day, count, date, bikeometer_id) %>% filter(month_day =='2020-03-12')
(16 + 140 + 34 + 51 + 250 + 131 + 108 + 28 + 228 + 54 + 48 + 408)/12
## [1] 124.6667
This matches the ‘avg_counts’ value for the ‘2020-03-12’ date!
counts_daily_filtered_2017_to_2019_grouped
counts_daily_filtered_2020_grouped
Let’s now join the ‘counts_daily_filtered_2017_to_2019_grouped’ and the ‘counts_daily_filtered_2020_grouped’ tables so we can graph them.
df <- merge(x = counts_daily_filtered_2017_to_2019_grouped, y = counts_daily_filtered_2020_grouped, by = 'month_day')
df
In order for this table to be easily read by R, we need to covert it from a wide table to a long table. This is the resource I used to understand how to do the conversion.
df.long <- pivot_longer(df, cols=2:3, names_to='year', values_to='counts')
df.long
line <- df.long %>% ggplot(aes(month_day, counts, group = year, color = year)) +
geom_area(aes(fill = year, group = year), alpha = 0.5, position = 'identity')
line
If you don’t include ‘position = ’identity’ then the counts will stack on top of each other instead of overlapping like above.
You can see the default arguments for a plot by using the args() function.
## function (mapping = NULL, data = NULL, stat = "identity", position = "stack",
## na.rm = FALSE, orientation = NA, show.legend = NA, inherit.aes = TRUE,
## ..., outline.type = "upper")
## NULL
library(ggthemes)
line <- line + theme_calc() + labs(x = 'Date', title = 'COVID-19 & Historic Total Bike Count')
line
##
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
library(ggplot2)
ggplotly(line)
Conclusion
It looks like there definitely was an increase for some days during the 2020 COVID Pandemic but the data isn’t as significant as the MnDOT plot.
This post concludes the work I set out to complete, but there is always more questions to answer. In the next post, I’ll investigate the GPS locations of each chosen Bikeometer and try to draw conclusions from that data.
LS0tDQp0aXRsZTogIlZpc3VhbGl6aW5nIEFybGluZ3RvbiBCaWtvbWV0ZXJzIg0Kc3VidGl0bGU6ICJQYXJ0IDY6IENyZWF0aW5nIHRoZSBWaXN1YWxpemF0aW9ucyINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDINCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIGhpZ2hsaWdodDogemVuYnVybg0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICBpbmNsdWRlczoNCiAgICAgIGluX2hlYWRlcjogaGVhZGVyLmh0bWwNCi0tLQ0KXCANClwgDQoNCiMgR29hbA0KDQpDcmVhdGUgYSBwb3N0IHdpdGggc2ltaWxhciBwYXJhbWV0ZXJzIHRvIHRoZSBbTWlubmVzb3RhIERPVCdzXShodHRwczovL3N0b3J5bWFwcy5hcmNnaXMuY29tL3N0b3JpZXMvNzBhM2E1NzAwM2I1NDFlZDhlOTExY2QxMTEwM2RjNmEpICpDb3ZpZC0xOSAmIEhpc3RvcmljIFRvdGFsIFRyYWZmaWMgQ291bnQqIGdyYXBoLg0KDQojIENvbm5lY3QgdG8gTXlTUUwgRGF0YWJhc2UNCg0KYGBge3IgZWNobyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmc9IEZBTFNFLCBpbmNsdWRlPVRSVUV9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShvZGJjKQ0KbGlicmFyeShyZXNoYXBlMikNCmxpYnJhcnkodGlkeXIpDQoNCmNvbiA8LSBkYkNvbm5lY3Qob2RiYzo6b2RiYygpLCBkc24gPSAiQmlrZV9NeVNRTCIpDQpgYGANCg0KIyBMb2FkIHRoZSB0YWJsZSBpbnRvIFINCg0KYGBge3J9DQpjb3VudHNfZGFpbHkgPC0gdGJsKGNvbiwgJ2NvdW50c19kYWlseScpDQpoZWFkKGNvdW50c19kYWlseSkNCmBgYA0KDQojIENvbWJpbmluZyAnSW4nIGFuZCAnT3V0JyBWYWx1ZXMNCg0KQmVsb3cgaXMgdGhlIGNvZGUgd2UgY3JlYXRlZCBpbiBJbiBbUGFydCA1XShodHRwczovL25hdGhhbnNwcm9qZWN0cy5jb20vcGFydF81X3BpY2tpbmdfYmlrZW9tZXRlcnNfdG9fZ3JhcGguaHRtbCksIHRvIGNvbWJpbmUgdGhlICdJJyBhbmQgJ08nIHZhbHVlcy4NCg0KYGBge3Igd2FybmluZz1GQUxTRX0NCmNvdW50c19kYWlseV90b3RhbCA8LSBjb3VudHNfZGFpbHkgJT4lIA0KICBncm91cF9ieShkYXRlLCBiaWtlb21ldGVyX2lkKSAlPiUgDQogIHN1bW1hcml6ZShjb3VudCA9IHN1bShjb3VudCksIGlzX3dlZWtlbmQgPSBpc193ZWVrZW5kLCANCiAgICAgICAgICAgIG1vbnRoID0gbW9udGgsIGRheSA9IGRheSwgeWVhciA9IHllYXIsIG1vbnRoX2RheSA9IG1vbnRoX2RheSkgJT4lIA0KICBjb2xsZWN0KCkNCmNvdW50c19kYWlseV90b3RhbA0KYGBgDQoNCmBgYHtyfQ0KY2xhc3MoY291bnRzX2RhaWx5X3RvdGFsJG1vbnRoX2RheSkNCmBgYA0KDQpJJ20gZ29pbmcgdG8gbWFuaXB1bGF0ZSB0aGUgKmNvdW50c19kYWlseV90b3RhbCogJ3RpYmJsZScgdXNpbmcgdGhlICptdXRhdGUoKSogZnVuY3Rpb24uDQoNCkkgcmVhbGl6ZWQgdGhhdCBJIHNob3VsZCBoYXZlIHVzZWQgYSBoeXBoZW4gaW5zdGVhZCBvZiBhbiB1bmRlcnNjb3JlIGZvciB0aGUgJ21vbnRoLWRheScgdmFsdWVzIHNvIGZpcnN0IEkgbmVlZCB0byBkbyBzb21lIGNoYXJhY3RlciByZXBsYWNlbWVudC4gSSdsbCB1c2UgdGhlICptdXRhdGUoKSogZnVuY3Rpb24gdG8gbWFrZSBjaGFuZ2VzIHRvIGNvbHVtbnMgYW5kIHBhc3MgaW4gdGhlICpnc3ViKCkqIGZ1bmN0aW9uIHRvIHJlcGxhY2UgdGhlIHVuZGVyc2NvcmUgd2l0aCBhIGh5cGhlbi4gICANCg0KVGhlbiwgd2Ugd2lsbCBhZGQgYW4gYXJiaXRyYXJ5IHllYXIgdG8gdGhlIGVuZCBvZiBtb250aF9kYXkgdG8gbWFrZSBpdCBlYXNpZXIgdG8gY29udmVydCB0byBhICpkYXRlKiBjbGFzcyBhbmQgc3Vic2VxdWVudGx5IGdyYXBoLg0KDQpgYGB7cn0NCmNvdW50c19kYWlseV90b3RhbCA8LSBjb3VudHNfZGFpbHlfdG90YWwgJT4lIG11dGF0ZShtb250aF9kYXkgPSBnc3ViKCJbX10iLCAiLSIsIG1vbnRoX2RheSkpDQoNCmNvdW50c19kYWlseV90b3RhbCA8LSBjb3VudHNfZGFpbHlfdG90YWwgJT4lIG11dGF0ZShtb250aF9kYXk9cGFzdGUobW9udGhfZGF5LCAnLTIwMjAnLCBzZXAgPSAnJykpDQpjb3VudHNfZGFpbHlfdG90YWwNCg0KYGBgDQpOb3csIGxldCdzIGNvbnZlcnQgbW9udGhfZGF5IHRvIGEgKmRhdGUqIGNsYXNzLg0KDQpgYGB7cn0NCmNsYXNzKGNvdW50c19kYWlseV90b3RhbCRtb250aF9kYXlbMV0pDQoNCmNvdW50c19kYWlseV90b3RhbCRtb250aF9kYXkgPC0gc3RycHRpbWUoY291bnRzX2RhaWx5X3RvdGFsJG1vbnRoX2RheSwgJyVtLSVkLSVZJykNCmNsYXNzKGNvdW50c19kYWlseV90b3RhbCRtb250aF9kYXlbMV0pDQoNCmNvdW50c19kYWlseV90b3RhbCRtb250aF9kYXkgPC0gYXMuRGF0ZShjb3VudHNfZGFpbHlfdG90YWwkbW9udGhfZGF5KQ0KY2xhc3MoY291bnRzX2RhaWx5X3RvdGFsJG1vbnRoX2RheVsxXSkNCmBgYA0KWW91IGNhbiBzZWUgdGhhdCB0aGUgY29sdW1uIGdvZXMgZnJvbSBjbGFzc2VzICdjaGFyYWN0ZXInIHRvICdQT1NJWGx0IFBPU0lYdCcgdG8gZmluYWxseSAnRGF0ZScuDQoNCmBgYHtyfQ0KY291bnRzX2RhaWx5X3RvdGFsDQpgYGANCg0KIyMjIE5vdGUgdG8gc2VsZg0KT3JpZ2luYWxseSwgY2xhc3MoY291bnRzX2RhaWx5X3RvdGFsJHllYXIsbW9udGgsZGF5ID09ICdTMyB2YWx1ZSBCaWdpbnQnIHdoaWNoIGRvZXNuJ3QgcGxheSBuaWNlbHkgd2l0aCBkcGx5ci4gQmUgc3VyZSB0byBjb252ZXJ0IG51bWJlciBjb2x1bW5zIGludG8gKmludCogYmVmb3JlIGFwcGx5aW5nIGZpbHRlcnMuIEFsc28sIHdoZW4gbXV0YXRpbmcgYSBjb2x1bW4sIGRvbid0IGFzc2lnbiB0aGUgbXV0YXRlIGxpa2UgdGhpczoNCg0KYGBge3IgZXZhbD1GQUxTRX0NCiMgRG9uJ3QgdXNlIHRoaXMNCmNvdW50c19kYWlseV90b3RhbCRtb250aF9kYXkgPC0gY291bnRzX2RhaWx5X3RvdGFsICU+JSBtdXRhdGUobW9udGhfZGF5ID0gZ3N1YigiW19dIiwgIi0iLCBtb250aF9kYXkpKQ0KDQojIFVzZSB0aGlzIQ0KY291bnRzX2RhaWx5X3RvdGFsIDwtIGNvdW50c19kYWlseV90b3RhbCAlPiUgbXV0YXRlKG1vbnRoX2RheSA9IGdzdWIoIltfXSIsICItIiwgbW9udGhfZGF5KSkNCg0KYGBgDQoNCiMgRmlsdGVyaW5nIGZvciBEYXRlIFJhbmdlIGFuZCBCaWtlb21ldGVyIElEDQoNCkluIGEgcHJldmlvdXMgcG9zdCwgSSBjaG9zZSBhIHRpbWUtZnJhbWUgYW5kIHdoaWNoIEJpa2VvbWV0ZXJzIHdlcmUgYmVzdCB0byBwbG90LiANCg0KYGBge3J9DQpjb3VudHNfZGFpbHlfZmlsdGVyZWRfMjAxN190b18yMDE5IDwtIGNvdW50c19kYWlseV90b3RhbCAlPiUgDQogIGZpbHRlcihkYXRlID49ICcyMDE3LTAzLTEyJyAmIGRhdGUgPD0gJzIwMTctMDUtMTUnICYgYmlrZW9tZXRlcl9pZCAlaW4lIGMoMTQsMTUsMTYsMTgsMjIsMzEsMzkpfCANCiAgICAgICAgIGRhdGUgPj0gJzIwMTgtMDMtMTInICYgZGF0ZSA8PSAnMjAxOC0wNS0xNScgJiBiaWtlb21ldGVyX2lkICVpbiUgYygxNCwxNSwxNiwxOCwyMiwzMSwzOSl8DQogICAgICAgICBkYXRlID49ICcyMDE5LTAzLTEyJyAmIGRhdGUgPD0gJzIwMTktMDUtMTUnICYgYmlrZW9tZXRlcl9pZCAlaW4lIGMoMTQsMTUsMTYsMTgsMjIsMzEsMzkpKQ0KY291bnRzX2RhaWx5X2ZpbHRlcmVkXzIwMTdfdG9fMjAxOQ0KYGBgDQpgYGB7cn0NCmNvdW50c19kYWlseV9maWx0ZXJlZF8yMDIwIDwtIGNvdW50c19kYWlseV90b3RhbCAlPiUgDQogIGZpbHRlcihkYXRlID49ICcyMDIwLTAzLTEyJyAmIGRhdGUgPD0gJzIwMjAtMDUtMTUnICYgYmlrZW9tZXRlcl9pZCAlaW4lIGMoMTQsMTUsMTYsMTgsMjIsMzEsMzkpKQ0KDQpjb3VudHNfZGFpbHlfZmlsdGVyZWRfMjAyMA0KYGBgDQoNCiMgR3JvdXAgdGhlIDIwMjAgQmlrZW9tZXRlciBjb3VudHMNCg0KTm93IHdlIGNhbiBjYW4gZ3JvdXAgdGhlIGNvdW50cyBieSB0YWtpbmcgdGhlaXIgYXZlcmFnZS4gSSdsbCByZW5hbWUgdGhlIGNvbHVtbiAnMjAyMF9jb3VudHMnIHRvIG1ha2UgaXQgZWFzaWVyIHRvIG1lcmdlIHRoZSB0YWJsZSBsYXRlci4NCg0KYGBge3J9DQpjb3VudHNfZGFpbHlfZmlsdGVyZWRfMjAyMF9ncm91cGVkIDwtIGNvdW50c19kYWlseV9maWx0ZXJlZF8yMDIwICU+JSBncm91cF9ieShtb250aF9kYXkpICU+JSBzdW1tYXJpc2UoJzIwMjBfY291bnRzJyA9IG1lYW4oY291bnQpKQ0KDQpjb3VudHNfZGFpbHlfZmlsdGVyZWRfMjAyMF9ncm91cGVkDQpgYGANCg0KIyBDcmVhdGluZyB0aGUgMjAxNy0yMDE5IERhaWx5IEF2ZXJhZ2UgY29sdW1uDQoNCk5vdyB0aGF0IHdlIGhhdmUgdHdvIHRhYmxlcywgb25lIHdpdGggMjAxNy0yMDE5IGRhdGEgYW5kIG9uZSB3aXRoIDIwMjAgZGF0YSwgbGV0J3MgYWRkIGEgY29sdW1uIHRvIHRoZSBjb3VudHNfZGFpbHlfZmlsdGVyZWRfMjAxN190b18yMDE5IHRhYmxlIHRoYXQgc2hvd3MgdGhlIGF2ZXJhZ2UgYWNyb3NzIGFsbCBCaWtlb21ldGVycy4NCg0KYGBge3J9DQpjb3VudHNfZGFpbHlfZmlsdGVyZWRfMjAxN190b18yMDE5X2dyb3VwZWQgPC0gY291bnRzX2RhaWx5X2ZpbHRlcmVkXzIwMTdfdG9fMjAxOSAlPiUgZ3JvdXBfYnkobW9udGhfZGF5KSAlPiUgc3VtbWFyaXNlKCcyMDE3LTIwMTlfYXZnX2NvdW50cycgPSBtZWFuKGNvdW50KSkNCg0KY291bnRzX2RhaWx5X2ZpbHRlcmVkXzIwMTdfdG9fMjAxOV9ncm91cGVkDQpgYGANCldlIHNob3VsZCBjaGVjayB0byBzZWUgaWYgdGhlIGF2ZXJhZ2UgaXMgYmVpbmcgdGFrZW4gY29ycmVjdGx5LiBXZSBjYW4gbWFudWFsbHkgY2hlY2sgdGhlIGZpcnN0ICdtb250aF9kYXknIGJlbG93Lg0KDQpgYGB7cn0NCmNvdW50c19kYWlseV9maWx0ZXJlZF8yMDE3X3RvXzIwMTkgJT4lIHNlbGVjdChtb250aF9kYXksIGNvdW50LCBkYXRlLCBiaWtlb21ldGVyX2lkKSAlPiUgIGZpbHRlcihtb250aF9kYXkgPT0nMjAyMC0wMy0xMicpIA0KYGBgDQoNCmBgYHtyfQ0KKDE2ICsgMTQwICsgMzQgKyA1MSArIDI1MCArIDEzMSArIDEwOCArIDI4ICsgMjI4ICsgNTQgKyA0OCArIDQwOCkvMTINCmBgYA0KDQpUaGlzIG1hdGNoZXMgdGhlICdhdmdfY291bnRzJyB2YWx1ZSBmb3IgdGhlICcyMDIwLTAzLTEyJyBkYXRlISANCg0KYGBge3J9DQpjb3VudHNfZGFpbHlfZmlsdGVyZWRfMjAxN190b18yMDE5X2dyb3VwZWQNCmBgYA0KDQpgYGB7cn0NCmNvdW50c19kYWlseV9maWx0ZXJlZF8yMDIwX2dyb3VwZWQNCmBgYA0KDQpMZXQncyBub3cgam9pbiB0aGUgJ2NvdW50c19kYWlseV9maWx0ZXJlZF8yMDE3X3RvXzIwMTlfZ3JvdXBlZCcgYW5kIHRoZSAnY291bnRzX2RhaWx5X2ZpbHRlcmVkXzIwMjBfZ3JvdXBlZCcgdGFibGVzIHNvIHdlIGNhbiBncmFwaCB0aGVtLg0KDQoNCmBgYHtyfQ0KZGYgPC0gbWVyZ2UoeCA9IGNvdW50c19kYWlseV9maWx0ZXJlZF8yMDE3X3RvXzIwMTlfZ3JvdXBlZCwgeSA9IGNvdW50c19kYWlseV9maWx0ZXJlZF8yMDIwX2dyb3VwZWQsIGJ5ID0gJ21vbnRoX2RheScpDQpkZg0KYGBgDQoNCkluIG9yZGVyIGZvciB0aGlzIHRhYmxlIHRvIGJlIGVhc2lseSByZWFkIGJ5IFIsIHdlIG5lZWQgdG8gY292ZXJ0IGl0IGZyb20gYSB3aWRlIHRhYmxlIHRvIGEgbG9uZyB0YWJsZS4gW1RoaXNdKCdodHRwczovL21naW1vbmQuZ2l0aHViLmlvL0VTMjE4L1dlZWswM2IuaHRtbCcpIGlzIHRoZSByZXNvdXJjZSBJIHVzZWQgdG8gdW5kZXJzdGFuZCBob3cgdG8gZG8gdGhlIGNvbnZlcnNpb24uDQoNCmBgYHtyfQ0KZGYubG9uZyA8LSBwaXZvdF9sb25nZXIoZGYsIGNvbHM9MjozLCBuYW1lc190bz0neWVhcicsIHZhbHVlc190bz0nY291bnRzJykNCmRmLmxvbmcNCmBgYA0KDQpgYGB7cn0NCmxpbmUgPC0gZGYubG9uZyAlPiUgZ2dwbG90KGFlcyhtb250aF9kYXksIGNvdW50cywgZ3JvdXAgPSB5ZWFyLCBjb2xvciA9IHllYXIpKSArDQogIGdlb21fYXJlYShhZXMoZmlsbCA9IHllYXIsIGdyb3VwID0geWVhciksIGFscGhhID0gMC41LCBwb3NpdGlvbiA9ICdpZGVudGl0eScpDQpsaW5lDQpgYGANCklmIHlvdSBkb24ndCBpbmNsdWRlICdwb3NpdGlvbiA9ICdpZGVudGl0eScgdGhlbiB0aGUgY291bnRzIHdpbGwgc3RhY2sgb24gdG9wIG9mIGVhY2ggb3RoZXIgaW5zdGVhZCBvZiBvdmVybGFwcGluZyBsaWtlIGFib3ZlLg0KDQpZb3UgY2FuIHNlZSB0aGUgZGVmYXVsdCBhcmd1bWVudHMgZm9yIGEgcGxvdCBieSB1c2luZyB0aGUgKmFyZ3MoKSogZnVuY3Rpb24uDQoNCmBgYHtyfQ0KYXJncyhnZW9tX2FyZWEpDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KGdndGhlbWVzKQ0KbGluZSA8LSBsaW5lICsgdGhlbWVfY2FsYygpICsgbGFicyh4ID0gJ0RhdGUnLCB0aXRsZSA9ICdDT1ZJRC0xOSAmIEhpc3RvcmljIFRvdGFsIEJpa2UgQ291bnQnKQ0KbGluZQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGdncGxvdDIpDQpnZ3Bsb3RseShsaW5lKQ0KYGBgDQoNCiMgQ29uY2x1c2lvbg0KDQpJdCBsb29rcyBsaWtlIHRoZXJlIGRlZmluaXRlbHkgd2FzIGFuIGluY3JlYXNlIGZvciBzb21lIGRheXMgZHVyaW5nIHRoZSAyMDIwIENPVklEIFBhbmRlbWljIGJ1dCB0aGUgZGF0YSBpc24ndCBhcyBzaWduaWZpY2FudCBhcyB0aGUgTW5ET1QgcGxvdC4gDQoNClRoaXMgcG9zdCBjb25jbHVkZXMgdGhlIHdvcmsgSSBzZXQgb3V0IHRvIGNvbXBsZXRlLCBidXQgdGhlcmUgaXMgYWx3YXlzIG1vcmUgcXVlc3Rpb25zIHRvIGFuc3dlci4gSW4gdGhlIG5leHQgcG9zdCwgSSdsbCBpbnZlc3RpZ2F0ZSB0aGUgR1BTIGxvY2F0aW9ucyBvZiBlYWNoIGNob3NlbiBCaWtlb21ldGVyIGFuZCB0cnkgdG8gZHJhdyBjb25jbHVzaW9ucyBmcm9tIHRoYXQgZGF0YS4=