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:

Recap

We’ve now covered the core dplyr functions: six “verbs” of common actions we may take with a data frame. There are lots of helper functions, variations, and special cases that can come up when using these functions, but we’ve covered the essentials.

All of these functions were for working with a single data frame. Next session we’ll talk about dplyr functions that will help us combine two different data frames together.

To learn more about some issues in computing police bias statistics, see https://fivethirtyeight.com/features/why-statistics-dont-capture-the-full-extent-of-the-systemic-bias-in-policing/ or from Northwestern researchers Prof. Redbird and Kat Albrecht, https://redbird.shinyapps.io/police-bias-map/.

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=