NOTE: The output of most of the R chunks isn’t included in the
HTML version of the file to keep it to a more reasonable file size. You
can run the code in R to see the output.
This is an R Markdown
document. Follow the link to learn more about R Markdown and the
notebook format used during the workshop.
Setup
library(tidyverse)
For this session, we’re learning four additional dplyr functions:
group_by, summarize, across, and arrange.
Data
We’re using the same data as session 1.
police <- read_csv("https://raw.githubusercontent.com/nuitrcs/r-tidyverse/main/data/ev_police.csv",
col_types=c("location"="c"))
Summarize
We’ll start with summarize()
(or
summarise()
- British spelling is accepted). We use
mutate()
when we want the output to have the same length as
the input – when we’re operating on the individual elements in a vector
- we want a value for every row in the data. When we want to condense
multiple values down to a single (or a few values), such as taking the
mean or standard deviation of a vector), we use summarize instead.
police %>%
mutate(vehicle_age = 2017-vehicle_year) %>% # computing a new variable first
summarize(mean_vehicle_age = mean(vehicle_age))
Note that even though there’s just one value, we get a tibble
returned. This is what to expect with the tidyverse.
As a side note, if we needed the single value (or a single vector),
we could pull()
it out of the tibble/data frame:
police %>%
mutate(vehicle_age = 2017-vehicle_year) %>% # computing a new variable first
summarize(mean_vehicle_age = mean(vehicle_age)) %>%
pull()
We can compute more than one summary measure at the same time:
police %>%
mutate(vehicle_age = 2017-vehicle_year) %>% # computing a new variable first
summarize(mean_vehicle_age = mean(vehicle_age),
sd_vehicle_age = sd(vehicle_age),
min_date = min(date),
max_date = max(date))
We get one column per summary variable we create. Once we group
below, we’ll see why we get the output in columns instead of rows.
EXERCISE 1
Use summarize to compute the min()
and
max()
vehicle_year
You can write your code here:
Across
If we want to apply the same summary functions to multiple columns in
our data frame, we can write out all of the summary commands explicitly,
or we can use across()
to select which variables to
summarize with which functions.
Let’s use the n_distinct()
function to count the number
of distinct values in each column (n_distinct(x)
is the
same as length(unique(x))
. This will help us see which
columns don’t have useful information because every value is the
same.
across()
selects columns using the helper functions you
could give to select()
directly. We’ll use
everything()
here to select all columns.
police %>%
summarize(across(everything(), n_distinct))
If you wanted to select columns using their names, put them in a
vector (so it’s a single input argument):
police %>%
summarize(across(c(date, time, location, beat, subject_age), n_distinct))
If you have the names of columns in a character vector, you can use
all_of()
function we learned in the lasts workshop:
myvars <- c("date","time","location","beat","subject_age")
police %>%
summarise(across(all_of(myvars), n_distinct))
If we want to apply multiple functions:
police %>%
summarize(across(!where(is.character), # select columns that are not of type character
list(min, max))) # take the min and max of each column
Judging from the column names, it might not be so clear which
function was applied to each column. To fix the names in the output,
explicitly name our summary functions in the list:
police %>%
summarize(across(!where(is.character), # select columns that are not of type character
list(min_val=min, max_val=max))) # take the min and max of each column
There are other options for output naming patterns available too.
EXERCISE 2
What if we want to do something to each column that is more
complicated than a single function? For example, counting missing
values, which you do with sum(is.na(x))
. You can write your
own function!
na_count <- function(x) {
sum(is.na(x))
}
# Example for a single vector:
na_count(police$subject_age)
## [1] 14792
Now use this new na_count()
function to count the
missing values in each column:
You can write your code here:
This is similar to using the apply() function in base R:
apply(police, 2, na_count)
Advanced Aside
Instead of creating the function ahead of time, we could define it as
part of the summarize call. We use .x to stand in for each column, and
we put ~ in front of the expression to signal we’re creating a function
in place (lambda function).
police %>%
summarize(across(everything(),
~sum(is.na(.x)))) # define a function here that operates on each column
This is an advanced technique outside of the scope of this workshop.
But if you see a ~ inside across()
, at least know that
someone is defining their own function inline.
Group By
With base R, when we want to compute summary measures or do other
computation on groups in our data (as defined by some grouping
variable), we use functions such as tapply()
or
aggregate()
. With dplyr, we can explicitly group our tibble
into subgroups. This isn’t very useful by itself, but it is often
combined with summarize()
to compute summary measures by
group.
First, what if we just group:
police %>%
group_by(outcome)
When we print this in the console,
# A tibble: 14,792 x 29
# Groups: outcome [2]
raw_row_number date time location beat subject_age subject_race subject_sex department_id
<dbl> <date> <tim> <dbl> <chr> <lgl> <chr> <chr> <dbl>
1 11249746 2017-01-01 00:56 60202 72 NA white male 13178
2 11249747 2017-01-01 04:43 60643 71 NA black male 13178
3 11249748 2017-01-01 09:21 60625 78 NA black female 13178
...
we see that it tells us that the tibble (data frame) is grouped by
outcome, and that there are two groups. It doesn’t rearrange the rows,
it just keeps track of the groups for us.
Now, combine with summarize. But first, let’s make the
vehicle_age
column we’ve been using actually part of the
police
dataset so that we don’t have to keep creating
it:
police <- mutate(police, vehicle_age = 2017-vehicle_year)
Now, group and summarize:
police %>%
group_by(subject_sex) %>%
summarize(mean_vehicle_age = mean(vehicle_age),
sd_vehicle_age = sd(vehicle_age))
Now we get one row for each group, and one column for each summary
measure.
We can group by multiple columns, and we’ll get all of the
combinations of values present across the columns:
police %>%
group_by(subject_sex, subject_race) %>%
summarize(mean_vehicle_age = mean(vehicle_age),
sd_vehicle_age = sd(vehicle_age))
Let’s compute the ratio of warnings to citations by subject_race -
note that we can use the variables we create in later expressions within
the same call to summarize()
:
police %>%
group_by(subject_race) %>%
summarize(warnings = sum(outcome == "warning"),
citations = sum(outcome == "citation"),
ratio = warnings/citations)
There’s considerable variation here, from 1.1 warnings for every
citation given to 2.2 warnings for every citation given.
EXERCISE 3
Compute the min()
and max()
vehicle_year
for each vehicle_make
.
You can write your code here:
Ungrouping
If you ever have a grouped data frame, you may need to ungroup it to
get rid of the groups. To do so, use ungroup()
:
police %>%
group_by(outcome) %>%
ungroup()
Slicing
One operation we could do with a grouped tibble is to select just
certain rows from each group. For example, we could use the
slice()
function to select the first row from each
group:
police %>%
select(outcome, everything()) %>% # to reorder columns for output
group_by(outcome) %>%
slice(1)
If you look at this output in the console, you’ll see the resulting
tibble still has groups in it. This is a case where you might want to
ungroup:
police %>%
select(outcome, everything()) %>% # to reorder columns for output
group_by(outcome) %>%
slice(1) %>%
ungroup()
Arrange
Finally, we come to arrange()
, which is how we sort the
rows in our data. We would mostly use this when viewing our data, but
it’s also useful when we need to compute a time series (lags and leads
in the data), when we want to select just a few rows from each group, or
any other order-sensitive transformations on our data.
police %>%
arrange(time)
To sort in reverse order, wrap the column name in
desc()
.
police %>%
arrange(desc(date))
Arrange by multiple columns, in order:
police %>%
arrange(date, desc(time))
An example where it matters: compute time between stops in the
dataset:
police %>%
arrange(date, time) %>%
mutate(datetime = lubridate::ymd_hms(paste(date, time)), # combine to single value
time_since_last = datetime - lag(datetime)) %>% # current time - previous time
select(datetime, time_since_last)
EXERCISE 4
Sort the data by vehicle_make
and then
vehicle_year
.
You can write your code here:
Count
A bonus function that I use frequently: count()
. It’s
how you’d get output similar to table()
By itself, it counts the number of rows:
police %>%
count()
If you supply the name of a column, it makes a table:
police %>%
count(subject_sex)
This is the same result as if you grouped the data first:
police %>%
group_by(subject_sex) %>%
count()
You can group by multiple columns directly with count:
police %>%
count(subject_sex, subject_race)
EXERCISE 5
How many times does each type of violation appear in the dataset?
Bonus: sort the results from most to least frequent. You can do this
with arrange()
or look at the documentation for
count()
to find another option.
You can write your code here:
Answers to the exercises
Exercise 1
police %>%
summarize(
min_vehicle_year = min(vehicle_year),
max_vehicle_year = max(vehicle_year)
)
Exercise 2
police %>%
summarize(
across(everything(),na_count)
)
Exercise 3
police %>%
group_by(vehicle_make) %>%
summarize(
min_vehicle_year = min(vehicle_year),
max_vehicle_year = max(vehicle_year)
)
Exercise 4
police %>%
arrange(vehicle_make, vehicle_year)
Exercise 5
police %>%
count(violation)
Bonus
# using arrange()
police %>%
count(violation) %>%
arrange(desc(n))
# using option from count()
police %>% count(violation, sort = TRUE)
LS0tDQp0aXRsZTogImRwbHlyIGdyb3VwaW5nIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZGVwdGg6IDINCmVkaXRvcl9vcHRpb25zOg0KICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lDQotLS0NCg0KYGBge3IsIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICBldmFsPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBlcnJvcj1GQUxTRQ0KKQ0KYGBgDQoNCipOT1RFOiBUaGUgb3V0cHV0IG9mIG1vc3Qgb2YgdGhlIFIgY2h1bmtzIGlzbid0IGluY2x1ZGVkIGluIHRoZSBIVE1MIHZlcnNpb24gb2YgdGhlIGZpbGUgdG8ga2VlcCBpdCB0byBhIG1vcmUgcmVhc29uYWJsZSBmaWxlIHNpemUuICBZb3UgY2FuIHJ1biB0aGUgY29kZSBpbiBSIHRvIHNlZSB0aGUgb3V0cHV0LioNCg0KVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cHM6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20vKSBkb2N1bWVudC4gIEZvbGxvdyB0aGUgbGluayB0byBsZWFybiBtb3JlIGFib3V0IFIgTWFya2Rvd24gYW5kIHRoZSBub3RlYm9vayBmb3JtYXQgdXNlZCBkdXJpbmcgdGhlIHdvcmtzaG9wLg0KDQojIFNldHVwDQoNCmBgYHtyLCBldmFsPVRSVUUsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQpGb3IgdGhpcyBzZXNzaW9uLCB3ZSdyZSBsZWFybmluZyBmb3VyIGFkZGl0aW9uYWwgZHBseXIgZnVuY3Rpb25zOiBncm91cF9ieSwgc3VtbWFyaXplLCBhY3Jvc3MsIGFuZCBhcnJhbmdlLg0KDQojIyBEYXRhDQoNCldlJ3JlIHVzaW5nIHRoZSBzYW1lIGRhdGEgYXMgc2Vzc2lvbiAxLiAgDQoNCmBgYHtyLCBldmFsPVRSVUV9DQpwb2xpY2UgPC0gcmVhZF9jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9udWl0cmNzL3ItdGlkeXZlcnNlL21haW4vZGF0YS9ldl9wb2xpY2UuY3N2IiwNCiAgICAgICAgICAgICAgICAgICBjb2xfdHlwZXM9YygibG9jYXRpb24iPSJjIikpDQpgYGANCg0KDQojIFN1bW1hcml6ZQ0KDQpXZSdsbCBzdGFydCB3aXRoIGBzdW1tYXJpemUoKWAgKG9yIGBzdW1tYXJpc2UoKWAgLSBCcml0aXNoIHNwZWxsaW5nIGlzIGFjY2VwdGVkKS4gIFdlIHVzZSBgbXV0YXRlKClgIHdoZW4gd2Ugd2FudCB0aGUgb3V0cHV0IHRvIGhhdmUgdGhlIHNhbWUgbGVuZ3RoIGFzIHRoZSBpbnB1dCAtLSB3aGVuIHdlJ3JlIG9wZXJhdGluZyBvbiB0aGUgaW5kaXZpZHVhbCBlbGVtZW50cyBpbiBhIHZlY3RvciAtIHdlIHdhbnQgYSB2YWx1ZSBmb3IgZXZlcnkgcm93IGluIHRoZSBkYXRhLiBXaGVuIHdlIHdhbnQgdG8gY29uZGVuc2UgbXVsdGlwbGUgdmFsdWVzIGRvd24gdG8gYSBzaW5nbGUgKG9yIGEgZmV3IHZhbHVlcyksIHN1Y2ggYXMgdGFraW5nIHRoZSBtZWFuIG9yIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBhIHZlY3RvciksIHdlIHVzZSBzdW1tYXJpemUgaW5zdGVhZC4NCg0KYGBge3J9DQpwb2xpY2UgJT4lIA0KICBtdXRhdGUodmVoaWNsZV9hZ2UgPSAyMDE3LXZlaGljbGVfeWVhcikgJT4lICMgY29tcHV0aW5nIGEgbmV3IHZhcmlhYmxlIGZpcnN0DQogIHN1bW1hcml6ZShtZWFuX3ZlaGljbGVfYWdlID0gbWVhbih2ZWhpY2xlX2FnZSkpDQpgYGANCg0KTm90ZSB0aGF0IGV2ZW4gdGhvdWdoIHRoZXJlJ3MganVzdCBvbmUgdmFsdWUsIHdlIGdldCBhIHRpYmJsZSByZXR1cm5lZC4gIFRoaXMgaXMgd2hhdCB0byBleHBlY3Qgd2l0aCB0aGUgdGlkeXZlcnNlLg0KDQpBcyBhIHNpZGUgbm90ZSwgaWYgd2UgbmVlZGVkIHRoZSBzaW5nbGUgdmFsdWUgKG9yIGEgc2luZ2xlIHZlY3RvciksIHdlIGNvdWxkIGBwdWxsKClgIGl0IG91dCBvZiB0aGUgdGliYmxlL2RhdGEgZnJhbWU6DQoNCmBgYHtyfQ0KcG9saWNlICU+JQ0KICBtdXRhdGUodmVoaWNsZV9hZ2UgPSAyMDE3LXZlaGljbGVfeWVhcikgJT4lICMgY29tcHV0aW5nIGEgbmV3IHZhcmlhYmxlIGZpcnN0DQogIHN1bW1hcml6ZShtZWFuX3ZlaGljbGVfYWdlID0gbWVhbih2ZWhpY2xlX2FnZSkpICU+JQ0KICBwdWxsKCkNCmBgYA0KDQpXZSBjYW4gY29tcHV0ZSBtb3JlIHRoYW4gb25lIHN1bW1hcnkgbWVhc3VyZSBhdCB0aGUgc2FtZSB0aW1lOg0KDQpgYGB7cn0NCnBvbGljZSAlPiUgDQogIG11dGF0ZSh2ZWhpY2xlX2FnZSA9IDIwMTctdmVoaWNsZV95ZWFyKSAlPiUgIyBjb21wdXRpbmcgYSBuZXcgdmFyaWFibGUgZmlyc3QNCiAgc3VtbWFyaXplKG1lYW5fdmVoaWNsZV9hZ2UgPSBtZWFuKHZlaGljbGVfYWdlKSwNCiAgICAgICAgICAgIHNkX3ZlaGljbGVfYWdlID0gc2QodmVoaWNsZV9hZ2UpLA0KICAgICAgICAgICAgbWluX2RhdGUgPSBtaW4oZGF0ZSksDQogICAgICAgICAgICBtYXhfZGF0ZSA9IG1heChkYXRlKSkNCmBgYA0KDQpXZSBnZXQgb25lIGNvbHVtbiBwZXIgc3VtbWFyeSB2YXJpYWJsZSB3ZSBjcmVhdGUuICBPbmNlIHdlIGdyb3VwIGJlbG93LCB3ZSdsbCBzZWUgd2h5IHdlIGdldCB0aGUgb3V0cHV0IGluIGNvbHVtbnMgaW5zdGVhZCBvZiByb3dzLg0KDQoNCiMjIyBFWEVSQ0lTRSAxDQoNClVzZSBzdW1tYXJpemUgdG8gY29tcHV0ZSB0aGUgYG1pbigpYCBhbmQgYG1heCgpYCBgdmVoaWNsZV95ZWFyYA0KDQpZb3UgY2FuIHdyaXRlIHlvdXIgY29kZSBoZXJlOg0KDQpgYGB7cn0NCg0KYGBgDQoNCg0KIyMgQWNyb3NzDQoNCklmIHdlIHdhbnQgdG8gYXBwbHkgdGhlIHNhbWUgc3VtbWFyeSBmdW5jdGlvbnMgdG8gbXVsdGlwbGUgY29sdW1ucyBpbiBvdXIgZGF0YSBmcmFtZSwgd2UgY2FuIHdyaXRlIG91dCBhbGwgb2YgdGhlIHN1bW1hcnkgY29tbWFuZHMgZXhwbGljaXRseSwgb3Igd2UgY2FuIHVzZSBgYWNyb3NzKClgIHRvIHNlbGVjdCB3aGljaCB2YXJpYWJsZXMgdG8gc3VtbWFyaXplIHdpdGggd2hpY2ggZnVuY3Rpb25zLg0KDQpMZXQncyB1c2UgdGhlIGBuX2Rpc3RpbmN0KClgIGZ1bmN0aW9uIHRvIGNvdW50IHRoZSBudW1iZXIgb2YgZGlzdGluY3QgdmFsdWVzIGluIGVhY2ggY29sdW1uIChgbl9kaXN0aW5jdCh4KWAgaXMgdGhlIHNhbWUgYXMgYGxlbmd0aCh1bmlxdWUoeCkpYC4gIFRoaXMgd2lsbCBoZWxwIHVzIHNlZSB3aGljaCBjb2x1bW5zIGRvbid0IGhhdmUgdXNlZnVsIGluZm9ybWF0aW9uIGJlY2F1c2UgZXZlcnkgdmFsdWUgaXMgdGhlIHNhbWUuDQoNCmBhY3Jvc3MoKWAgc2VsZWN0cyBjb2x1bW5zIHVzaW5nIHRoZSBoZWxwZXIgZnVuY3Rpb25zIHlvdSBjb3VsZCBnaXZlIHRvIGBzZWxlY3QoKWAgZGlyZWN0bHkuICBXZSdsbCB1c2UgYGV2ZXJ5dGhpbmcoKWAgaGVyZSB0byBzZWxlY3QgYWxsIGNvbHVtbnMuDQoNCmBgYHtyfQ0KcG9saWNlICU+JQ0KICBzdW1tYXJpemUoYWNyb3NzKGV2ZXJ5dGhpbmcoKSwgbl9kaXN0aW5jdCkpDQpgYGANCg0KSWYgeW91IHdhbnRlZCB0byBzZWxlY3QgY29sdW1ucyB1c2luZyB0aGVpciBuYW1lcywgcHV0IHRoZW0gaW4gYSB2ZWN0b3IgKHNvIGl0J3MgYSBzaW5nbGUgaW5wdXQgYXJndW1lbnQpOg0KDQpgYGB7cn0NCnBvbGljZSAlPiUNCiAgc3VtbWFyaXplKGFjcm9zcyhjKGRhdGUsIHRpbWUsIGxvY2F0aW9uLCBiZWF0LCBzdWJqZWN0X2FnZSksIG5fZGlzdGluY3QpKQ0KYGBgDQoNCklmIHlvdSBoYXZlIHRoZSBuYW1lcyBvZiBjb2x1bW5zIGluIGEgY2hhcmFjdGVyIHZlY3RvciwgeW91IGNhbiB1c2UgYGFsbF9vZigpYCBmdW5jdGlvbiB3ZSBsZWFybmVkIGluIHRoZSBsYXN0cyB3b3Jrc2hvcDoNCg0KYGBge3J9DQpteXZhcnMgPC0gYygiZGF0ZSIsInRpbWUiLCJsb2NhdGlvbiIsImJlYXQiLCJzdWJqZWN0X2FnZSIpDQpwb2xpY2UgJT4lDQogIHN1bW1hcmlzZShhY3Jvc3MoYWxsX29mKG15dmFycyksIG5fZGlzdGluY3QpKQ0KYGBgDQoNCg0KSWYgd2Ugd2FudCB0byBhcHBseSBtdWx0aXBsZSBmdW5jdGlvbnM6DQoNCmBgYHtyfQ0KcG9saWNlICU+JQ0KICBzdW1tYXJpemUoYWNyb3NzKCF3aGVyZShpcy5jaGFyYWN0ZXIpLCAjIHNlbGVjdCBjb2x1bW5zIHRoYXQgYXJlIG5vdCBvZiB0eXBlIGNoYXJhY3Rlcg0KICAgICAgICAgICAgICAgICAgIGxpc3QobWluLCBtYXgpKSkgICAjIHRha2UgdGhlIG1pbiBhbmQgbWF4IG9mIGVhY2ggY29sdW1uDQpgYGANCg0KSnVkZ2luZyBmcm9tIHRoZSBjb2x1bW4gbmFtZXMsIGl0IG1pZ2h0IG5vdCBiZSBzbyBjbGVhciB3aGljaCBmdW5jdGlvbiB3YXMgYXBwbGllZCB0byBlYWNoIGNvbHVtbi4NClRvIGZpeCB0aGUgbmFtZXMgaW4gdGhlIG91dHB1dCwgZXhwbGljaXRseSBuYW1lIG91ciBzdW1tYXJ5IGZ1bmN0aW9ucyBpbiB0aGUgbGlzdDoNCg0KYGBge3J9DQpwb2xpY2UgJT4lDQogIHN1bW1hcml6ZShhY3Jvc3MoIXdoZXJlKGlzLmNoYXJhY3RlciksICMgc2VsZWN0IGNvbHVtbnMgdGhhdCBhcmUgbm90IG9mIHR5cGUgY2hhcmFjdGVyDQogICAgICAgICAgICAgICAgICAgbGlzdChtaW5fdmFsPW1pbiwgbWF4X3ZhbD1tYXgpKSkgICAjIHRha2UgdGhlIG1pbiBhbmQgbWF4IG9mIGVhY2ggY29sdW1uDQpgYGANCg0KVGhlcmUgYXJlIG90aGVyIG9wdGlvbnMgZm9yIG91dHB1dCBuYW1pbmcgcGF0dGVybnMgYXZhaWxhYmxlIHRvby4NCg0KIyMjIEVYRVJDSVNFIDINCg0KV2hhdCBpZiB3ZSB3YW50IHRvIGRvIHNvbWV0aGluZyB0byBlYWNoIGNvbHVtbiB0aGF0IGlzIG1vcmUgY29tcGxpY2F0ZWQgdGhhbiBhIHNpbmdsZSBmdW5jdGlvbj8gIEZvciBleGFtcGxlLCBjb3VudGluZyBtaXNzaW5nIHZhbHVlcywgd2hpY2ggeW91IGRvIHdpdGggYHN1bShpcy5uYSh4KSlgLiAgWW91IGNhbiB3cml0ZSB5b3VyIG93biBmdW5jdGlvbiENCg0KYGBge3IsIGV2YWw9VFJVRX0NCm5hX2NvdW50IDwtIGZ1bmN0aW9uKHgpIHsNCiAgc3VtKGlzLm5hKHgpKQ0KfQ0KDQojIEV4YW1wbGUgZm9yIGEgc2luZ2xlIHZlY3RvcjoNCm5hX2NvdW50KHBvbGljZSRzdWJqZWN0X2FnZSkNCmBgYA0KDQpOb3cgdXNlIHRoaXMgbmV3IGBuYV9jb3VudCgpYCBmdW5jdGlvbiB0byBjb3VudCB0aGUgbWlzc2luZyB2YWx1ZXMgaW4gZWFjaCBjb2x1bW46DQoNCllvdSBjYW4gd3JpdGUgeW91ciBjb2RlIGhlcmU6DQoNCmBgYHtyfQ0KDQpgYGANCg0KVGhpcyBpcyBzaW1pbGFyIHRvIHVzaW5nIHRoZSBhcHBseSgpIGZ1bmN0aW9uIGluIGJhc2UgUjoNCg0KYGBge3J9DQphcHBseShwb2xpY2UsIDIsIG5hX2NvdW50KQ0KYGBgDQoNCiMjIyMgQWR2YW5jZWQgQXNpZGUNCg0KSW5zdGVhZCBvZiBjcmVhdGluZyB0aGUgZnVuY3Rpb24gYWhlYWQgb2YgdGltZSwgd2UgY291bGQgZGVmaW5lIGl0IGFzIHBhcnQgb2YgdGhlIHN1bW1hcml6ZSBjYWxsLiBXZSB1c2UgLnggdG8gc3RhbmQgaW4gZm9yIGVhY2ggY29sdW1uLCBhbmQgd2UgcHV0IH4gaW4gZnJvbnQgb2YgdGhlIGV4cHJlc3Npb24gdG8gc2lnbmFsIHdl4oCZcmUgY3JlYXRpbmcgYSBmdW5jdGlvbiBpbiBwbGFjZSAobGFtYmRhIGZ1bmN0aW9uKS4NCg0KYGBge3J9DQpwb2xpY2UgJT4lDQogIHN1bW1hcml6ZShhY3Jvc3MoZXZlcnl0aGluZygpLA0KICAgICAgICAgICAgICAgICAgIH5zdW0oaXMubmEoLngpKSkpICAjIGRlZmluZSBhIGZ1bmN0aW9uIGhlcmUgdGhhdCBvcGVyYXRlcyBvbiBlYWNoIGNvbHVtbg0KYGBgDQoNClRoaXMgaXMgYW4gYWR2YW5jZWQgdGVjaG5pcXVlIG91dHNpZGUgb2YgdGhlIHNjb3BlIG9mIHRoaXMgd29ya3Nob3AuICBCdXQgaWYgeW91IHNlZSBhIH4gaW5zaWRlIGBhY3Jvc3MoKWAsIGF0IGxlYXN0IGtub3cgdGhhdCBzb21lb25lIGlzIGRlZmluaW5nIHRoZWlyIG93biBmdW5jdGlvbiBpbmxpbmUuICANCg0KIyBHcm91cCBCeQ0KDQpXaXRoIGJhc2UgUiwgd2hlbiB3ZSB3YW50IHRvIGNvbXB1dGUgc3VtbWFyeSBtZWFzdXJlcyBvciBkbyBvdGhlciBjb21wdXRhdGlvbiBvbiBncm91cHMgaW4gb3VyIGRhdGEgKGFzIGRlZmluZWQgYnkgc29tZSBncm91cGluZyB2YXJpYWJsZSksIHdlIHVzZSBmdW5jdGlvbnMgc3VjaCBhcyBgdGFwcGx5KClgIG9yIGBhZ2dyZWdhdGUoKWAuICBXaXRoIGRwbHlyLCB3ZSBjYW4gZXhwbGljaXRseSBncm91cCBvdXIgdGliYmxlIGludG8gc3ViZ3JvdXBzLiAgVGhpcyBpc24ndCB2ZXJ5IHVzZWZ1bCBieSBpdHNlbGYsIGJ1dCBpdCBpcyBvZnRlbiBjb21iaW5lZCB3aXRoIGBzdW1tYXJpemUoKWAgdG8gY29tcHV0ZSBzdW1tYXJ5IG1lYXN1cmVzIGJ5IGdyb3VwLg0KDQpGaXJzdCwgd2hhdCBpZiB3ZSBqdXN0IGdyb3VwOg0KDQpgYGB7cn0NCnBvbGljZSAlPiUNCiAgZ3JvdXBfYnkob3V0Y29tZSkNCmBgYA0KDQpXaGVuIHdlIHByaW50IHRoaXMgaW4gdGhlIGNvbnNvbGUsIA0KDQpgYGANCiMgQSB0aWJibGU6IDE0LDc5MiB4IDI5DQojIEdyb3VwczogICBvdXRjb21lIFsyXQ0KICAgcmF3X3Jvd19udW1iZXIgZGF0ZSAgICAgICB0aW1lICBsb2NhdGlvbiBiZWF0ICBzdWJqZWN0X2FnZSBzdWJqZWN0X3JhY2Ugc3ViamVjdF9zZXggZGVwYXJ0bWVudF9pZA0KICAgICAgICAgICAgPGRibD4gPGRhdGU+ICAgICA8dGltPiAgICA8ZGJsPiA8Y2hyPiA8bGdsPiAgICAgICA8Y2hyPiAgICAgICAgPGNocj4gICAgICAgICAgICAgICA8ZGJsPg0KIDEgICAgICAgMTEyNDk3NDYgMjAxNy0wMS0wMSAwMDo1NiAgICA2MDIwMiA3MiAgICBOQSAgICAgICAgICB3aGl0ZSAgICAgICAgbWFsZSAgICAgICAgICAgICAgICAxMzE3OA0KIDIgICAgICAgMTEyNDk3NDcgMjAxNy0wMS0wMSAwNDo0MyAgICA2MDY0MyA3MSAgICBOQSAgICAgICAgICBibGFjayAgICAgICAgbWFsZSAgICAgICAgICAgICAgICAxMzE3OA0KIDMgICAgICAgMTEyNDk3NDggMjAxNy0wMS0wMSAwOToyMSAgICA2MDYyNSA3OCAgICBOQSAgICAgICAgICBibGFjayAgICAgICAgZmVtYWxlICAgICAgICAgICAgICAxMzE3OA0KLi4uDQpgYGANCg0Kd2Ugc2VlIHRoYXQgaXQgdGVsbHMgdXMgdGhhdCB0aGUgdGliYmxlIChkYXRhIGZyYW1lKSBpcyBncm91cGVkIGJ5IG91dGNvbWUsIGFuZCB0aGF0IHRoZXJlIGFyZSB0d28gZ3JvdXBzLiAgSXQgZG9lc24ndCByZWFycmFuZ2UgdGhlIHJvd3MsIGl0IGp1c3Qga2VlcHMgdHJhY2sgb2YgdGhlIGdyb3VwcyBmb3IgdXMuIA0KDQpOb3csIGNvbWJpbmUgd2l0aCBzdW1tYXJpemUuICBCdXQgZmlyc3QsIGxldCdzIG1ha2UgdGhlIGB2ZWhpY2xlX2FnZWAgY29sdW1uIHdlJ3ZlIGJlZW4gdXNpbmcgYWN0dWFsbHkgcGFydCBvZiB0aGUgYHBvbGljZWAgZGF0YXNldCBzbyB0aGF0IHdlIGRvbid0IGhhdmUgdG8ga2VlcCBjcmVhdGluZyBpdDoNCg0KYGBge3IsIGV2YWw9VFJVRX0NCnBvbGljZSA8LSBtdXRhdGUocG9saWNlLCB2ZWhpY2xlX2FnZSA9IDIwMTctdmVoaWNsZV95ZWFyKQ0KYGBgDQoNCk5vdywgZ3JvdXAgYW5kIHN1bW1hcml6ZToNCg0KYGBge3J9DQpwb2xpY2UgJT4lIA0KICBncm91cF9ieShzdWJqZWN0X3NleCkgJT4lDQogIHN1bW1hcml6ZShtZWFuX3ZlaGljbGVfYWdlID0gbWVhbih2ZWhpY2xlX2FnZSksDQogICAgICAgICAgICBzZF92ZWhpY2xlX2FnZSA9IHNkKHZlaGljbGVfYWdlKSkNCmBgYA0KDQpOb3cgd2UgZ2V0IG9uZSByb3cgZm9yIGVhY2ggZ3JvdXAsIGFuZCBvbmUgY29sdW1uIGZvciBlYWNoIHN1bW1hcnkgbWVhc3VyZS4NCg0KV2UgY2FuIGdyb3VwIGJ5IG11bHRpcGxlIGNvbHVtbnMsIGFuZCB3ZSdsbCBnZXQgYWxsIG9mIHRoZSBjb21iaW5hdGlvbnMgb2YgdmFsdWVzIHByZXNlbnQgYWNyb3NzIHRoZSBjb2x1bW5zOg0KDQpgYGB7cn0NCnBvbGljZSAlPiUgDQogIGdyb3VwX2J5KHN1YmplY3Rfc2V4LCBzdWJqZWN0X3JhY2UpICU+JQ0KICBzdW1tYXJpemUobWVhbl92ZWhpY2xlX2FnZSA9IG1lYW4odmVoaWNsZV9hZ2UpLA0KICAgICAgICAgICAgc2RfdmVoaWNsZV9hZ2UgPSBzZCh2ZWhpY2xlX2FnZSkpDQpgYGANCg0KTGV0J3MgY29tcHV0ZSB0aGUgcmF0aW8gb2Ygd2FybmluZ3MgdG8gY2l0YXRpb25zIGJ5IHN1YmplY3RfcmFjZSAtIG5vdGUgdGhhdCB3ZSBjYW4gdXNlIHRoZSB2YXJpYWJsZXMgd2UgY3JlYXRlIGluIGxhdGVyIGV4cHJlc3Npb25zIHdpdGhpbiB0aGUgc2FtZSBjYWxsIHRvIGBzdW1tYXJpemUoKWA6DQoNCmBgYHtyfQ0KcG9saWNlICU+JQ0KICBncm91cF9ieShzdWJqZWN0X3JhY2UpICU+JQ0KICBzdW1tYXJpemUod2FybmluZ3MgPSBzdW0ob3V0Y29tZSA9PSAid2FybmluZyIpLA0KICAgICAgICAgICAgY2l0YXRpb25zID0gc3VtKG91dGNvbWUgPT0gImNpdGF0aW9uIiksIA0KICAgICAgICAgICAgcmF0aW8gPSB3YXJuaW5ncy9jaXRhdGlvbnMpDQpgYGANCg0KVGhlcmUncyBjb25zaWRlcmFibGUgdmFyaWF0aW9uIGhlcmUsIGZyb20gMS4xIHdhcm5pbmdzIGZvciBldmVyeSBjaXRhdGlvbiBnaXZlbiB0byAyLjIgd2FybmluZ3MgZm9yIGV2ZXJ5IGNpdGF0aW9uIGdpdmVuLg0KDQojIyMgRVhFUkNJU0UgMw0KDQpDb21wdXRlIHRoZSBgbWluKClgIGFuZCBgbWF4KClgIGB2ZWhpY2xlX3llYXJgIGZvciBlYWNoIGB2ZWhpY2xlX21ha2VgLg0KDQpZb3UgY2FuIHdyaXRlIHlvdXIgY29kZSBoZXJlOg0KDQpgYGB7cn0NCg0KYGBgDQoNCg0KIyMgVW5ncm91cGluZw0KDQpJZiB5b3UgZXZlciBoYXZlIGEgZ3JvdXBlZCBkYXRhIGZyYW1lLCB5b3UgbWF5IG5lZWQgdG8gdW5ncm91cCBpdCB0byBnZXQgcmlkIG9mIHRoZSBncm91cHMuICBUbyBkbyBzbywgdXNlIGB1bmdyb3VwKClgOg0KDQpgYGB7cn0NCnBvbGljZSAlPiUgDQogIGdyb3VwX2J5KG91dGNvbWUpICU+JQ0KICB1bmdyb3VwKCkNCmBgYA0KDQoNCiMjIFNsaWNpbmcNCg0KT25lIG9wZXJhdGlvbiB3ZSBjb3VsZCBkbyB3aXRoIGEgZ3JvdXBlZCB0aWJibGUgaXMgdG8gc2VsZWN0IGp1c3QgY2VydGFpbiByb3dzIGZyb20gZWFjaCBncm91cC4gIEZvciBleGFtcGxlLCB3ZSBjb3VsZCB1c2UgdGhlIGBzbGljZSgpYCBmdW5jdGlvbiB0byBzZWxlY3QgdGhlIGZpcnN0IHJvdyBmcm9tIGVhY2ggZ3JvdXA6DQoNCmBgYHtyfQ0KcG9saWNlICU+JQ0KICBzZWxlY3Qob3V0Y29tZSwgZXZlcnl0aGluZygpKSAlPiUgICMgdG8gcmVvcmRlciBjb2x1bW5zIGZvciBvdXRwdXQNCiAgZ3JvdXBfYnkob3V0Y29tZSkgJT4lDQogIHNsaWNlKDEpDQpgYGANCg0KSWYgeW91IGxvb2sgYXQgdGhpcyBvdXRwdXQgaW4gdGhlIGNvbnNvbGUsIHlvdSdsbCBzZWUgdGhlIHJlc3VsdGluZyB0aWJibGUgc3RpbGwgaGFzIGdyb3VwcyBpbiBpdC4gIFRoaXMgaXMgYSBjYXNlIHdoZXJlIHlvdSBtaWdodCB3YW50IHRvIHVuZ3JvdXA6DQoNCmBgYHtyfQ0KcG9saWNlICU+JQ0KICBzZWxlY3Qob3V0Y29tZSwgZXZlcnl0aGluZygpKSAlPiUgICMgdG8gcmVvcmRlciBjb2x1bW5zIGZvciBvdXRwdXQNCiAgZ3JvdXBfYnkob3V0Y29tZSkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkNCmBgYA0KDQoNCiMgQXJyYW5nZQ0KDQpGaW5hbGx5LCB3ZSBjb21lIHRvIGBhcnJhbmdlKClgLCB3aGljaCBpcyBob3cgd2Ugc29ydCB0aGUgcm93cyBpbiBvdXIgZGF0YS4gIFdlIHdvdWxkIG1vc3RseSB1c2UgdGhpcyB3aGVuIHZpZXdpbmcgb3VyIGRhdGEsIGJ1dCBpdCdzIGFsc28gdXNlZnVsIHdoZW4gd2UgbmVlZCB0byBjb21wdXRlIGEgdGltZSBzZXJpZXMgKGxhZ3MgYW5kIGxlYWRzIGluIHRoZSBkYXRhKSwgd2hlbiB3ZSB3YW50IHRvIHNlbGVjdCBqdXN0IGEgZmV3IHJvd3MgZnJvbSBlYWNoIGdyb3VwLCBvciBhbnkgb3RoZXIgb3JkZXItc2Vuc2l0aXZlIHRyYW5zZm9ybWF0aW9ucyBvbiBvdXIgZGF0YS4NCg0KYGBge3J9DQpwb2xpY2UgJT4lDQogIGFycmFuZ2UodGltZSkNCmBgYA0KDQpUbyBzb3J0IGluIHJldmVyc2Ugb3JkZXIsIHdyYXAgdGhlIGNvbHVtbiBuYW1lIGluIGBkZXNjKClgLiAgDQoNCmBgYHtyfQ0KcG9saWNlICU+JQ0KICBhcnJhbmdlKGRlc2MoZGF0ZSkpDQpgYGANCg0KQXJyYW5nZSBieSBtdWx0aXBsZSBjb2x1bW5zLCBpbiBvcmRlcjoNCg0KYGBge3J9DQpwb2xpY2UgJT4lDQogIGFycmFuZ2UoZGF0ZSwgZGVzYyh0aW1lKSkNCmBgYA0KDQpBbiBleGFtcGxlIHdoZXJlIGl0IG1hdHRlcnM6IGNvbXB1dGUgdGltZSBiZXR3ZWVuIHN0b3BzIGluIHRoZSBkYXRhc2V0Og0KDQpgYGB7cn0NCnBvbGljZSAlPiUNCiAgYXJyYW5nZShkYXRlLCB0aW1lKSAlPiUNCiAgbXV0YXRlKGRhdGV0aW1lID0gbHVicmlkYXRlOjp5bWRfaG1zKHBhc3RlKGRhdGUsIHRpbWUpKSwgICMgY29tYmluZSB0byBzaW5nbGUgdmFsdWUNCiAgICAgICAgIHRpbWVfc2luY2VfbGFzdCA9IGRhdGV0aW1lIC0gbGFnKGRhdGV0aW1lKSkgJT4lICAjIGN1cnJlbnQgdGltZSAtIHByZXZpb3VzIHRpbWUNCiAgc2VsZWN0KGRhdGV0aW1lLCB0aW1lX3NpbmNlX2xhc3QpDQpgYGANCg0KDQojIyMgRVhFUkNJU0UgNA0KDQpTb3J0IHRoZSBkYXRhIGJ5IGB2ZWhpY2xlX21ha2VgIGFuZCB0aGVuIGB2ZWhpY2xlX3llYXJgLg0KDQpZb3UgY2FuIHdyaXRlIHlvdXIgY29kZSBoZXJlOg0KDQpgYGB7cn0NCg0KYGBgDQoNCg0KIyBDb3VudA0KDQpBIGJvbnVzIGZ1bmN0aW9uIHRoYXQgSSB1c2UgZnJlcXVlbnRseTogYGNvdW50KClgLiAgSXQncyBob3cgeW91J2QgZ2V0IG91dHB1dCBzaW1pbGFyIHRvIGB0YWJsZSgpYA0KDQpCeSBpdHNlbGYsIGl0IGNvdW50cyB0aGUgbnVtYmVyIG9mIHJvd3M6DQoNCmBgYHtyfQ0KcG9saWNlICU+JQ0KICBjb3VudCgpDQpgYGANCg0KSWYgeW91IHN1cHBseSB0aGUgbmFtZSBvZiBhIGNvbHVtbiwgaXQgbWFrZXMgYSB0YWJsZToNCg0KYGBge3J9DQpwb2xpY2UgJT4lDQogIGNvdW50KHN1YmplY3Rfc2V4KQ0KYGBgDQoNClRoaXMgaXMgdGhlIHNhbWUgcmVzdWx0IGFzIGlmIHlvdSBncm91cGVkIHRoZSBkYXRhIGZpcnN0Og0KDQpgYGB7cn0NCnBvbGljZSAlPiUNCiAgZ3JvdXBfYnkoc3ViamVjdF9zZXgpICU+JQ0KICBjb3VudCgpDQpgYGANCg0KWW91IGNhbiBncm91cCBieSBtdWx0aXBsZSBjb2x1bW5zIGRpcmVjdGx5IHdpdGggY291bnQ6DQoNCmBgYHtyfQ0KcG9saWNlICU+JQ0KICBjb3VudChzdWJqZWN0X3NleCwgc3ViamVjdF9yYWNlKQ0KYGBgDQoNCg0KIyMjIEVYRVJDSVNFIDUNCg0KSG93IG1hbnkgdGltZXMgZG9lcyBlYWNoIHR5cGUgb2YgdmlvbGF0aW9uIGFwcGVhciBpbiB0aGUgZGF0YXNldD8gIEJvbnVzOiBzb3J0IHRoZSByZXN1bHRzIGZyb20gbW9zdCB0byBsZWFzdCBmcmVxdWVudC4gIFlvdSBjYW4gZG8gdGhpcyB3aXRoIGBhcnJhbmdlKClgIG9yIGxvb2sgYXQgdGhlIGRvY3VtZW50YXRpb24gZm9yIGBjb3VudCgpYCB0byBmaW5kIGFub3RoZXIgb3B0aW9uLg0KDQpZb3UgY2FuIHdyaXRlIHlvdXIgY29kZSBoZXJlOg0KDQpgYGB7cn0NCg0KYGBgDQoNCg0KIyBSZWNhcA0KDQpXZSd2ZSBub3cgY292ZXJlZCB0aGUgY29yZSBkcGx5ciBmdW5jdGlvbnM6IHNpeCAidmVyYnMiIG9mIGNvbW1vbiBhY3Rpb25zIHdlIG1heSB0YWtlIHdpdGggYSBkYXRhIGZyYW1lLiAgVGhlcmUgYXJlIGxvdHMgb2YgaGVscGVyIGZ1bmN0aW9ucywgdmFyaWF0aW9ucywgYW5kIHNwZWNpYWwgY2FzZXMgdGhhdCBjYW4gY29tZSB1cCB3aGVuIHVzaW5nIHRoZXNlIGZ1bmN0aW9ucywgYnV0IHdlJ3ZlIGNvdmVyZWQgdGhlIGVzc2VudGlhbHMuICANCg0KQWxsIG9mIHRoZXNlIGZ1bmN0aW9ucyB3ZXJlIGZvciB3b3JraW5nIHdpdGggYSBzaW5nbGUgZGF0YSBmcmFtZS4gIE5leHQgc2Vzc2lvbiB3ZSdsbCB0YWxrIGFib3V0IGRwbHlyIGZ1bmN0aW9ucyB0aGF0IHdpbGwgaGVscCB1cyBjb21iaW5lIHR3byBkaWZmZXJlbnQgZGF0YSBmcmFtZXMgdG9nZXRoZXIuICANCg0KVG8gbGVhcm4gbW9yZSBhYm91dCBzb21lIGlzc3VlcyBpbiBjb21wdXRpbmcgcG9saWNlIGJpYXMgc3RhdGlzdGljcywgc2VlIGh0dHBzOi8vZml2ZXRoaXJ0eWVpZ2h0LmNvbS9mZWF0dXJlcy93aHktc3RhdGlzdGljcy1kb250LWNhcHR1cmUtdGhlLWZ1bGwtZXh0ZW50LW9mLXRoZS1zeXN0ZW1pYy1iaWFzLWluLXBvbGljaW5nLyBvciBmcm9tIE5vcnRod2VzdGVybiByZXNlYXJjaGVycyBQcm9mLiBSZWRiaXJkIGFuZCBLYXQgQWxicmVjaHQsIGh0dHBzOi8vcmVkYmlyZC5zaGlueWFwcHMuaW8vcG9saWNlLWJpYXMtbWFwLy4gIA0KDQojIEFuc3dlcnMgdG8gdGhlIGV4ZXJjaXNlcw0KDQojIyMgRXhlcmNpc2UgMQ0KDQpgYGB7cn0NCnBvbGljZSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIG1pbl92ZWhpY2xlX3llYXIgPSBtaW4odmVoaWNsZV95ZWFyKSwNCiAgICBtYXhfdmVoaWNsZV95ZWFyID0gbWF4KHZlaGljbGVfeWVhcikNCiAgKQ0KYGBgDQoNCiMjIyBFeGVyY2lzZSAyDQoNCmBgYHtyfQ0KcG9saWNlICU+JQ0KICBzdW1tYXJpemUoDQogICAgYWNyb3NzKGV2ZXJ5dGhpbmcoKSxuYV9jb3VudCkNCiAgKQ0KYGBgDQoNCiMjIyBFeGVyY2lzZSAzDQoNCmBgYHtyfQ0KcG9saWNlICU+JQ0KICBncm91cF9ieSh2ZWhpY2xlX21ha2UpICU+JQ0KICBzdW1tYXJpemUoDQogICAgbWluX3ZlaGljbGVfeWVhciA9IG1pbih2ZWhpY2xlX3llYXIpLA0KICAgIG1heF92ZWhpY2xlX3llYXIgPSBtYXgodmVoaWNsZV95ZWFyKQ0KICApDQpgYGANCg0KIyMjIEV4ZXJjaXNlIDQNCg0KYGBge3J9DQpwb2xpY2UgJT4lDQogIGFycmFuZ2UodmVoaWNsZV9tYWtlLCB2ZWhpY2xlX3llYXIpDQpgYGANCg0KIyMjIEV4ZXJjaXNlIDUNCg0KYGBge3J9DQpwb2xpY2UgJT4lDQogIGNvdW50KHZpb2xhdGlvbikNCmBgYA0KDQojIyMjIEJvbnVzDQoNCmBgYHtyfQ0KIyB1c2luZyBhcnJhbmdlKCkNCnBvbGljZSAlPiUNCiAgY291bnQodmlvbGF0aW9uKSAlPiUNCiAgYXJyYW5nZShkZXNjKG4pKQ0KDQojIHVzaW5nIG9wdGlvbiBmcm9tIGNvdW50KCkNCnBvbGljZSAlPiUgY291bnQodmlvbGF0aW9uLCBzb3J0ID0gVFJVRSkNCmBgYA0KDQo=