This is a short tutorial outlining the syntax of the four basic data tidying functions of the tidyr package, namely:

  • pivot_longer(),
  • pivot_wider(),
  • separate(), and
  • unite().

1 Tidy data

Recall that tidy data is tabular data that is organised such that:

  1. each observation has its own row,
  2. each variable has its own column, and
  3. each value has its own cell.

This is sometimes referred to ‘tall’ or ‘long’ form data because of its shape. By contrast you may have ‘wide’ data which, for example, has columns containing measurements of the same variable but taken at different time points.

Both wide or tidy are valid ways to store data but I’d argue that tidy data is easier to manipulate. This is the philosophy behind the omnibus of packages that constitutes the tidyverse bundle of R packages.

2 COVID-19 time-series

The example dataset used in this tutorial is curated by the Center for Systems Science and Engineering at Johns Hopkins University, Whiting School of Engineering.

It is time series data updated nightly with the count of COVID-19 cases globally, grouped by various geographical regions. We can read the .CSV directly into a data frame.

library(readr)

url_string <- "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv"

covid19_cases <- read_csv(url(url_string))

In this tutorial we’ll just examine the counts for Australian cases only.

library(dplyr)

au_cases <- covid19_cases %>%
  filter(`Country/Region` == "Australia") %>%
  select(State = `Province/State`, ends_with("20")) %>%
  mutate(State = factor(State))
au_cases

Notice that the column headers (aside from the first) are dates in m/d/yy format. This is a clue that this data frame is in ‘wide’ format since the column names, rather than being generic variable names, are themselves encoded with data about the observations.

In other words, these headers are not just labels indicating which column contains dates, they themselves are the dates.

3 Pivoting Longer

Let’s reorganise this data frame into ‘long’ format using the pivot_longer() function in tidyr.

The cols = argument specifies which columns we want to pivot. Here we want to pivot all columns except the State column. This is achieved using the exclaimation prefix ! before the column name we want to exclude. The names_to = argument indicates the name of the new column for storing what were previously column names. The third argument values_to = is the name of another new column for storing the observation’s value.

library(tidyr)

au_cases_long <- au_cases %>%
  pivot_longer(cols = !State,
               names_to = "Date",
               values_to = "NumCases")
au_cases_long

Observe that each of the 8 rows of 108 elements in the wide data frame has been pivoted into 107 rows of 2 elements in the long data frame.

Also notice that the new Date column is assumed to beof type character.

4 Separating

In the Date column of the long data frame, observe that it comprises a tuple of integers corresponding to the month, day, and year of the observation. For the purposes of this tutorial let’s break out each of these date components into its own column. This is achieved using separate().

col = indicates which column we wish to split namely Date, into = is a vector of new column names ordered by how the components appear in the column being split, the separator character is given to sep =, and setting convert = to TRUE instructs R to automatically convert the new columns into an appropriate data type, e.g., integer.

au_cases_tidy <- au_cases_long %>%
  separate(col = Date, 
           into = c("Month", "Day", "Year"), 
           sep = "/",
           convert = TRUE)
au_cases_tidy

We now have each date component stored as integers in its own column, ready to be plotted.

For example, perhaps we want to plot the number of cases in Australia as observed on the first day of each month since records started. Having tidy data makes the filter()ing straightforward and ggplot()ing.

library(ggplot2)

au_cases_tidy %>%
  filter(Day == 1) %>%
  ggplot(aes(x = Month, y = NumCases, colour = State)) +
    geom_line(size = 2, alpha = 0.5) +
    ggtitle("Australian COVID-19 cases at month's start in 2020") +
    ylab("No. of cases") +
    guides(colour = guide_legend(title = "State/Territory"))

However, rather than expressing months as numerals it would make sense to store them as proper date objects.

5 Uniting

Having split the original Date column into components, we can reconstitute it back using the unite() function and follow it up by converting the dates to ISO format: YYYY-MM-DD.

The col = argument specifies the name of the newly reconstituted column, the next arguments are the column names being united together in their desired order, and the sep = indicates a separator character that should be inserted between components.

We then pipe %>% this into a mutate() command and use the dmy() function in the lubridate package to convert the character strings into true date objects.

library(lubridate)

au_cases_tidy_date <- au_cases_tidy %>%
  unite(col = "Date",
        Day, Month, Year, 
        sep = "-") %>%
  mutate(Date = dmy(Date))
au_cases_tidy_date

Now with proper date objects, the x-axis of our plots become more intuitive.

au_cases_tidy_date %>%
  ggplot(aes(x = Date, y = NumCases, colour = State)) +
    geom_line(size = 2, alpha = 0.5) +
    ggtitle(label = "Australian COVID-19 cases in April 2020", 
            subtitle = "Note logarithmic scale") +
    ylab("No. of cases") +
    guides(colour = guide_legend(title = "State/Territory")) +
    scale_y_log10()

6 Pivoting wider

Finally, we can revert to something similar to our original dataset by applying pivot_wider().

The id_cols = argument specifies which columns to include in the pivot (in this case all of them), names_from = indicates from which column to get the new column names, and values_from = tells R from which current column to fill in the new columns.

au_cases_wide <- au_cases_tidy_date %>%
  pivot_wider(id_cols = everything(),
              names_from = Date,
              values_from = NumCases)
au_cases_wide

This final data frame is similar to the first wide data frame except that the column names are now dates in ISO format.

7 One fell swoop

Using some of the other arguments to pivot_longer() we could in a single command perform the pivoting and separation of the Date without using separate().

The *_ptypes = arguments indicate what data types the resultant columns should be.

au_cases %>%
  pivot_longer(cols = !State,
               names_to = c("Month", "Day", "Year"),
               names_sep = "/",
               names_transform = list(Month = as.integer, Day = as.integer, Year = as.integer),
               values_to = "NumCases",
               values_transform = list(NumCases = as.integer)) %>%
  select(State, Day, Month, Year, NumCases)
au_cases

8 Duplicated entries and non-tidy data

To conform to the ‘tidy’ principles of data organisation we’ll sometimes end up introducing some duplication of values within a column, e.g., the State column in the above examples.

The original wide data frame had 8 \(\times\) 149 = 1192 entries compared with 1184 \(\times\) 5 = 5920 entries in the tidied data frame.

This is a potential downside for adopting tidy data formats and it reminds us that non-tidy data isn’t necessarily to be avoided as something inherently deficient. Data often comes to us organised in a particular structure because it makes most sense to a human reader or the data collection process.

The tidyr package is designed to assist with wrangling such data into a consistent representation that can leverage the vectorisation features in R more broadly.

9 Resources

LS0tDQp0aXRsZTogJ2B0aWR5cmAgVHV0b3JpYWwnDQphdXRob3I6ICJTaGloIENoaW5nIEZ1Ig0KZGF0ZTogIk1heSAyMDIwIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmOiBwYWdlZCANCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZGVwdGg6IDMNCiAgICB0b2NfZmxvYXQ6IA0KICAgICAgY29sbGFwc2VkOiB0cnVlDQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgdGhlbWU6IHJlYWRhYmxlDQogICAgaGlnaGxpZ2h0OiBoYWRkb2NrDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0Ka25pdDogDQogIChmdW5jdGlvbihpbnB1dF9maWxlLCBlbmNvZGluZykgew0KICAgIHJtYXJrZG93bjo6cmVuZGVyKGlucHV0X2ZpbGUsDQogICAgICAgICAgICAgICAgICAgICAgZW5jb2Rpbmc9ZW5jb2RpbmcsDQogICAgICAgICAgICAgICAgICAgICAgb3V0cHV0X2ZpbGU9ZmlsZS5wYXRoKGRpcm5hbWUoaW5wdXRfZmlsZSksICdkb2NzJywgJ2luZGV4Lmh0bWwnKSl9KQ0KLS0tDQoNClRoaXMgaXMgYSBzaG9ydCB0dXRvcmlhbCBvdXRsaW5pbmcgdGhlIHN5bnRheCBvZiB0aGUgZm91ciBiYXNpYyBkYXRhIHRpZHlpbmcgZnVuY3Rpb25zIG9mIHRoZSBgdGlkeXJgIHBhY2thZ2UsIG5hbWVseToNCg0KLSBgcGl2b3RfbG9uZ2VyKClgLA0KLSBgcGl2b3Rfd2lkZXIoKWAsDQotIGBzZXBhcmF0ZSgpYCwgYW5kDQotIGB1bml0ZSgpYC4NCg0KDQojIFRpZHkgZGF0YQ0KDQpSZWNhbGwgdGhhdCB0aWR5IGRhdGEgaXMgdGFidWxhciBkYXRhIHRoYXQgaXMgb3JnYW5pc2VkIHN1Y2ggdGhhdDoNCg0KMS4gZWFjaCBvYnNlcnZhdGlvbiBoYXMgaXRzIG93biByb3csDQoyLiBlYWNoIHZhcmlhYmxlIGhhcyBpdHMgb3duIGNvbHVtbiwgYW5kDQozLiBlYWNoIHZhbHVlIGhhcyBpdHMgb3duIGNlbGwuDQoNClRoaXMgaXMgc29tZXRpbWVzIHJlZmVycmVkIHRvICd0YWxsJyBvciAnbG9uZycgZm9ybSBkYXRhIGJlY2F1c2Ugb2YgaXRzIHNoYXBlLiBCeSBjb250cmFzdCB5b3UgbWF5IGhhdmUgJ3dpZGUnIGRhdGEgd2hpY2gsIGZvciBleGFtcGxlLCBoYXMgY29sdW1ucyBjb250YWluaW5nIG1lYXN1cmVtZW50cyBvZiB0aGUgKnNhbWUqIHZhcmlhYmxlIGJ1dCB0YWtlbiBhdCBkaWZmZXJlbnQgdGltZSBwb2ludHMuIA0KDQpCb3RoIHdpZGUgb3IgdGlkeSBhcmUgdmFsaWQgd2F5cyB0byBzdG9yZSBkYXRhIGJ1dCBJJ2QgYXJndWUgdGhhdCB0aWR5IGRhdGEgaXMgZWFzaWVyIHRvIG1hbmlwdWxhdGUuIFRoaXMgaXMgdGhlIHBoaWxvc29waHkgYmVoaW5kIHRoZSBvbW5pYnVzIG9mIHBhY2thZ2VzIHRoYXQgY29uc3RpdHV0ZXMgdGhlIGB0aWR5dmVyc2VgIGJ1bmRsZSBvZiBSIHBhY2thZ2VzLg0KDQojIENPVklELTE5IHRpbWUtc2VyaWVzDQoNClRoZSBleGFtcGxlIGRhdGFzZXQgdXNlZCBpbiB0aGlzIHR1dG9yaWFsIGlzIGN1cmF0ZWQgYnkgdGhlIFtDZW50ZXIgZm9yIFN5c3RlbXMgU2NpZW5jZSBhbmQgRW5naW5lZXJpbmddKGh0dHBzOi8vc3lzdGVtcy5qaHUuZWR1LykgYXQgSm9obnMgSG9wa2lucyBVbml2ZXJzaXR5LCBXaGl0aW5nIFNjaG9vbCBvZiBFbmdpbmVlcmluZy4gDQoNCkl0IGlzIHRpbWUgc2VyaWVzIGRhdGEgdXBkYXRlZCBuaWdodGx5IHdpdGggdGhlIGNvdW50IG9mIENPVklELTE5IGNhc2VzIGdsb2JhbGx5LCBncm91cGVkIGJ5IHZhcmlvdXMgZ2VvZ3JhcGhpY2FsIHJlZ2lvbnMuIFdlIGNhbiByZWFkIHRoZSBgLkNTVmAgZGlyZWN0bHkgaW50byBhIGRhdGEgZnJhbWUuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KHJlYWRyKQ0KDQp1cmxfc3RyaW5nIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQ1NTRUdJU2FuZERhdGEvQ09WSUQtMTkvbWFzdGVyL2Nzc2VfY292aWRfMTlfZGF0YS9jc3NlX2NvdmlkXzE5X3RpbWVfc2VyaWVzL3RpbWVfc2VyaWVzX2NvdmlkMTlfZGVhdGhzX2dsb2JhbC5jc3YiDQoNCmNvdmlkMTlfY2FzZXMgPC0gcmVhZF9jc3YodXJsKHVybF9zdHJpbmcpKQ0KYGBgDQoNCkluIHRoaXMgdHV0b3JpYWwgd2UnbGwganVzdCBleGFtaW5lIHRoZSBjb3VudHMgZm9yIEF1c3RyYWxpYW4gY2FzZXMgb25seS4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkoZHBseXIpDQoNCmF1X2Nhc2VzIDwtIGNvdmlkMTlfY2FzZXMgJT4lDQogIGZpbHRlcihgQ291bnRyeS9SZWdpb25gID09ICJBdXN0cmFsaWEiKSAlPiUNCiAgc2VsZWN0KFN0YXRlID0gYFByb3ZpbmNlL1N0YXRlYCwgZW5kc193aXRoKCIyMCIpKSAlPiUNCiAgbXV0YXRlKFN0YXRlID0gZmFjdG9yKFN0YXRlKSkNCmF1X2Nhc2VzDQoNCmBgYA0KDQpOb3RpY2UgdGhhdCB0aGUgY29sdW1uIGhlYWRlcnMgKGFzaWRlIGZyb20gdGhlIGZpcnN0KSBhcmUgZGF0ZXMgaW4gYG0vZC95eWAgZm9ybWF0LiBUaGlzIGlzIGEgY2x1ZSB0aGF0IHRoaXMgZGF0YSBmcmFtZSBpcyBpbiAnd2lkZScgZm9ybWF0IHNpbmNlIHRoZSBjb2x1bW4gbmFtZXMsIHJhdGhlciB0aGFuIGJlaW5nIGdlbmVyaWMgdmFyaWFibGUgbmFtZXMsIGFyZSB0aGVtc2VsdmVzIGVuY29kZWQgd2l0aCBkYXRhIGFib3V0IHRoZSBvYnNlcnZhdGlvbnMuIA0KDQpJbiBvdGhlciB3b3JkcywgdGhlc2UgaGVhZGVycyBhcmUgbm90IGp1c3QgbGFiZWxzIGluZGljYXRpbmcgd2hpY2ggY29sdW1uIF9jb250YWluc18gZGF0ZXMsIHRoZXkgdGhlbXNlbHZlcyBfYXJlXyB0aGUgZGF0ZXMuDQoNCiMgUGl2b3RpbmcgTG9uZ2VyDQoNCkxldCdzIHJlb3JnYW5pc2UgdGhpcyBkYXRhIGZyYW1lIGludG8gJ2xvbmcnIGZvcm1hdCB1c2luZyB0aGUgYHBpdm90X2xvbmdlcigpYCBmdW5jdGlvbiBpbiBgdGlkeXJgLg0KDQpUaGUgYGNvbHMgPWAgYXJndW1lbnQgc3BlY2lmaWVzIHdoaWNoIGNvbHVtbnMgd2Ugd2FudCB0byBwaXZvdC4gSGVyZSB3ZSB3YW50IHRvIHBpdm90IGFsbCBjb2x1bW5zIF9leGNlcHRfIHRoZSBgU3RhdGVgIGNvbHVtbi4gVGhpcyBpcyBhY2hpZXZlZCB1c2luZyB0aGUgZXhjbGFpbWF0aW9uIHByZWZpeCBgIWAgYmVmb3JlIHRoZSBjb2x1bW4gbmFtZSB3ZSB3YW50IHRvIGV4Y2x1ZGUuIFRoZSBgbmFtZXNfdG8gPWAgYXJndW1lbnQgaW5kaWNhdGVzIHRoZSBuYW1lIG9mIHRoZSBfbmV3XyBjb2x1bW4gZm9yIHN0b3Jpbmcgd2hhdCB3ZXJlIF9wcmV2aW91c2x5XyBjb2x1bW4gbmFtZXMuIFRoZSB0aGlyZCBhcmd1bWVudCBgdmFsdWVzX3RvID1gIGlzIHRoZSBuYW1lIG9mIGFub3RoZXIgbmV3IGNvbHVtbiBmb3Igc3RvcmluZyB0aGUgb2JzZXJ2YXRpb24ncyB2YWx1ZS4NCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHlyKQ0KDQphdV9jYXNlc19sb25nIDwtIGF1X2Nhc2VzICU+JQ0KICBwaXZvdF9sb25nZXIoY29scyA9ICFTdGF0ZSwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIkRhdGUiLA0KICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gIk51bUNhc2VzIikNCmF1X2Nhc2VzX2xvbmcNCmBgYA0KDQpPYnNlcnZlIHRoYXQgZWFjaCBvZiB0aGUgOCByb3dzIG9mIDEwOCBlbGVtZW50cyBpbiB0aGUgd2lkZSBkYXRhIGZyYW1lIGhhcyBiZWVuIHBpdm90ZWQgaW50byAxMDcgcm93cyBvZiAyIGVsZW1lbnRzIGluIHRoZSBsb25nIGRhdGEgZnJhbWUuIA0KDQpBbHNvIG5vdGljZSB0aGF0IHRoZSBuZXcgYERhdGVgIGNvbHVtbiBpcyBhc3N1bWVkIHRvIGJlb2YgdHlwZSBgY2hhcmFjdGVyYC4NCg0KIyBTZXBhcmF0aW5nDQoNCkluIHRoZSBgRGF0ZWAgY29sdW1uIG9mIHRoZSBsb25nIGRhdGEgZnJhbWUsIG9ic2VydmUgdGhhdCBpdCBjb21wcmlzZXMgYSB0dXBsZSBvZiBpbnRlZ2VycyBjb3JyZXNwb25kaW5nIHRvIHRoZSBtb250aCwgZGF5LCBhbmQgeWVhciBvZiB0aGUgb2JzZXJ2YXRpb24uIEZvciB0aGUgcHVycG9zZXMgb2YgdGhpcyB0dXRvcmlhbCBsZXQncyBicmVhayBvdXQgZWFjaCBvZiB0aGVzZSBkYXRlIGNvbXBvbmVudHMgaW50byBpdHMgb3duIGNvbHVtbi4gVGhpcyBpcyBhY2hpZXZlZCB1c2luZyBgc2VwYXJhdGUoKWAuDQoNCmBjb2wgPWAgaW5kaWNhdGVzIHdoaWNoIGNvbHVtbiB3ZSB3aXNoIHRvIHNwbGl0IG5hbWVseSBgRGF0ZWAsIGBpbnRvID1gIGlzIGEgdmVjdG9yIG9mIG5ldyBjb2x1bW4gbmFtZXMgb3JkZXJlZCBieSBob3cgdGhlIGNvbXBvbmVudHMgYXBwZWFyIGluIHRoZSBjb2x1bW4gYmVpbmcgc3BsaXQsIHRoZSBzZXBhcmF0b3IgY2hhcmFjdGVyIGlzIGdpdmVuIHRvIGBzZXAgPWAsIGFuZCBzZXR0aW5nIGBjb252ZXJ0ID1gIHRvIGBUUlVFYCBpbnN0cnVjdHMgUiB0byBhdXRvbWF0aWNhbGx5IGNvbnZlcnQgdGhlIG5ldyBjb2x1bW5zIGludG8gYW4gYXBwcm9wcmlhdGUgZGF0YSB0eXBlLCBlLmcuLCBpbnRlZ2VyLg0KDQpgYGB7cn0NCmF1X2Nhc2VzX3RpZHkgPC0gYXVfY2FzZXNfbG9uZyAlPiUNCiAgc2VwYXJhdGUoY29sID0gRGF0ZSwgDQogICAgICAgICAgIGludG8gPSBjKCJNb250aCIsICJEYXkiLCAiWWVhciIpLCANCiAgICAgICAgICAgc2VwID0gIi8iLA0KICAgICAgICAgICBjb252ZXJ0ID0gVFJVRSkNCmF1X2Nhc2VzX3RpZHkNCmBgYA0KDQpXZSBub3cgaGF2ZSBlYWNoIGRhdGUgY29tcG9uZW50IHN0b3JlZCBhcyBpbnRlZ2VycyBpbiBpdHMgb3duIGNvbHVtbiwgcmVhZHkgdG8gYmUgcGxvdHRlZC4gDQoNCkZvciBleGFtcGxlLCBwZXJoYXBzIHdlIHdhbnQgdG8gcGxvdCB0aGUgbnVtYmVyIG9mIGNhc2VzIGluIEF1c3RyYWxpYSBhcyBvYnNlcnZlZCBvbiB0aGUgZmlyc3QgZGF5IG9mIGVhY2ggbW9udGggc2luY2UgcmVjb3JkcyBzdGFydGVkLiBIYXZpbmcgdGlkeSBkYXRhIG1ha2VzIHRoZSBgZmlsdGVyKClgaW5nIHN0cmFpZ2h0Zm9yd2FyZCBhbmQgYGdncGxvdCgpYGluZy4NCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQoNCmF1X2Nhc2VzX3RpZHkgJT4lDQogIGZpbHRlcihEYXkgPT0gMSkgJT4lDQogIGdncGxvdChhZXMoeCA9IE1vbnRoLCB5ID0gTnVtQ2FzZXMsIGNvbG91ciA9IFN0YXRlKSkgKw0KICAgIGdlb21fbGluZShzaXplID0gMiwgYWxwaGEgPSAwLjUpICsNCiAgICBnZ3RpdGxlKCJBdXN0cmFsaWFuIENPVklELTE5IGNhc2VzIGF0IG1vbnRoJ3Mgc3RhcnQgaW4gMjAyMCIpICsNCiAgICB5bGFiKCJOby4gb2YgY2FzZXMiKSArDQogICAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZCh0aXRsZSA9ICJTdGF0ZS9UZXJyaXRvcnkiKSkNCmBgYA0KDQpIb3dldmVyLCByYXRoZXIgdGhhbiBleHByZXNzaW5nIG1vbnRocyBhcyBudW1lcmFscyBpdCB3b3VsZCBtYWtlIHNlbnNlIHRvIHN0b3JlIHRoZW0gYXMgcHJvcGVyIGRhdGUgb2JqZWN0cy4NCg0KIyBVbml0aW5nDQoNCkhhdmluZyBzcGxpdCB0aGUgb3JpZ2luYWwgYERhdGVgIGNvbHVtbiBpbnRvIGNvbXBvbmVudHMsIHdlIGNhbiByZWNvbnN0aXR1dGUgaXQgYmFjayB1c2luZyB0aGUgYHVuaXRlKClgIGZ1bmN0aW9uIGFuZCBmb2xsb3cgaXQgdXAgYnkgY29udmVydGluZyB0aGUgZGF0ZXMgdG8gSVNPIGZvcm1hdDogYFlZWVktTU0tRERgLg0KDQpUaGUgYGNvbCA9YCBhcmd1bWVudCBzcGVjaWZpZXMgdGhlIG5hbWUgb2YgdGhlIG5ld2x5IHJlY29uc3RpdHV0ZWQgY29sdW1uLCB0aGUgbmV4dCBhcmd1bWVudHMgYXJlIHRoZSBjb2x1bW4gbmFtZXMgYmVpbmcgdW5pdGVkIHRvZ2V0aGVyIGluIHRoZWlyIGRlc2lyZWQgb3JkZXIsIGFuZCB0aGUgYHNlcCA9YCBpbmRpY2F0ZXMgYSBzZXBhcmF0b3IgY2hhcmFjdGVyIHRoYXQgc2hvdWxkIGJlIGluc2VydGVkIGJldHdlZW4gY29tcG9uZW50cy4NCg0KV2UgdGhlbiBwaXBlIGAlPiVgIHRoaXMgaW50byBhIGBtdXRhdGUoKWAgY29tbWFuZCBhbmQgdXNlIHRoZSBgZG15KClgIGZ1bmN0aW9uIGluIHRoZSBgbHVicmlkYXRlYCBwYWNrYWdlIHRvIGNvbnZlcnQgdGhlIGNoYXJhY3RlciBzdHJpbmdzIGludG8gdHJ1ZSBkYXRlIG9iamVjdHMuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCg0KYXVfY2FzZXNfdGlkeV9kYXRlIDwtIGF1X2Nhc2VzX3RpZHkgJT4lDQogIHVuaXRlKGNvbCA9ICJEYXRlIiwNCiAgICAgICAgRGF5LCBNb250aCwgWWVhciwgDQogICAgICAgIHNlcCA9ICItIikgJT4lDQogIG11dGF0ZShEYXRlID0gZG15KERhdGUpKQ0KYXVfY2FzZXNfdGlkeV9kYXRlDQpgYGANCg0KTm93IHdpdGggcHJvcGVyIGRhdGUgb2JqZWN0cywgdGhlIHgtYXhpcyBvZiBvdXIgcGxvdHMgYmVjb21lIG1vcmUgaW50dWl0aXZlLg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFfQ0KYXVfY2FzZXNfdGlkeV9kYXRlICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBEYXRlLCB5ID0gTnVtQ2FzZXMsIGNvbG91ciA9IFN0YXRlKSkgKw0KICAgIGdlb21fbGluZShzaXplID0gMiwgYWxwaGEgPSAwLjUpICsNCiAgICBnZ3RpdGxlKGxhYmVsID0gIkF1c3RyYWxpYW4gQ09WSUQtMTkgY2FzZXMgaW4gQXByaWwgMjAyMCIsIA0KICAgICAgICAgICAgc3VidGl0bGUgPSAiTm90ZSBsb2dhcml0aG1pYyBzY2FsZSIpICsNCiAgICB5bGFiKCJOby4gb2YgY2FzZXMiKSArDQogICAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZCh0aXRsZSA9ICJTdGF0ZS9UZXJyaXRvcnkiKSkgKw0KICAgIHNjYWxlX3lfbG9nMTAoKQ0KYGBgDQoNCiMgUGl2b3Rpbmcgd2lkZXINCg0KRmluYWxseSwgd2UgY2FuIHJldmVydCB0byBzb21ldGhpbmcgc2ltaWxhciB0byBvdXIgb3JpZ2luYWwgZGF0YXNldCBieSBhcHBseWluZyBgcGl2b3Rfd2lkZXIoKWAuDQoNClRoZSBgaWRfY29scyA9YCBhcmd1bWVudCBzcGVjaWZpZXMgd2hpY2ggY29sdW1ucyB0byBpbmNsdWRlIGluIHRoZSBwaXZvdCAoaW4gdGhpcyBjYXNlIGFsbCBvZiB0aGVtKSwgYG5hbWVzX2Zyb20gPWAgaW5kaWNhdGVzIGZyb20gd2hpY2ggY29sdW1uIHRvIGdldCB0aGUgX25ld18gY29sdW1uIG5hbWVzLCBhbmQgYHZhbHVlc19mcm9tID0gYCB0ZWxscyBSIGZyb20gd2hpY2ggY3VycmVudCBjb2x1bW4gdG8gZmlsbCBpbiB0aGUgbmV3IGNvbHVtbnMuDQoNCmBgYHtyfQ0KYXVfY2FzZXNfd2lkZSA8LSBhdV9jYXNlc190aWR5X2RhdGUgJT4lDQogIHBpdm90X3dpZGVyKGlkX2NvbHMgPSBldmVyeXRoaW5nKCksDQogICAgICAgICAgICAgIG5hbWVzX2Zyb20gPSBEYXRlLA0KICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IE51bUNhc2VzKQ0KYXVfY2FzZXNfd2lkZQ0KYGBgDQoNClRoaXMgZmluYWwgZGF0YSBmcmFtZSBpcyBzaW1pbGFyIHRvIHRoZSBmaXJzdCB3aWRlIGRhdGEgZnJhbWUgZXhjZXB0IHRoYXQgdGhlIGNvbHVtbiBuYW1lcyBhcmUgbm93IGRhdGVzIGluIElTTyBmb3JtYXQuDQoNCiMgT25lIGZlbGwgc3dvb3ANCg0KVXNpbmcgc29tZSBvZiB0aGUgb3RoZXIgYXJndW1lbnRzIHRvIGBwaXZvdF9sb25nZXIoKWAgd2UgY291bGQgaW4gYSBzaW5nbGUgY29tbWFuZCBwZXJmb3JtIHRoZSBwaXZvdGluZyBhbmQgc2VwYXJhdGlvbiBvZiB0aGUgYERhdGVgIHdpdGhvdXQgdXNpbmcgYHNlcGFyYXRlKClgLg0KDQpUaGUgYCpfcHR5cGVzID1gIGFyZ3VtZW50cyBpbmRpY2F0ZSB3aGF0IGRhdGEgdHlwZXMgdGhlIHJlc3VsdGFudCBjb2x1bW5zIHNob3VsZCBiZS4NCg0KYGBge3J9DQphdV9jYXNlcyAlPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAhU3RhdGUsDQogICAgICAgICAgICAgICBuYW1lc190byA9IGMoIk1vbnRoIiwgIkRheSIsICJZZWFyIiksDQogICAgICAgICAgICAgICBuYW1lc19zZXAgPSAiLyIsDQogICAgICAgICAgICAgICBuYW1lc190cmFuc2Zvcm0gPSBsaXN0KE1vbnRoID0gYXMuaW50ZWdlciwgRGF5ID0gYXMuaW50ZWdlciwgWWVhciA9IGFzLmludGVnZXIpLA0KICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gIk51bUNhc2VzIiwNCiAgICAgICAgICAgICAgIHZhbHVlc190cmFuc2Zvcm0gPSBsaXN0KE51bUNhc2VzID0gYXMuaW50ZWdlcikpICU+JQ0KICBzZWxlY3QoU3RhdGUsIERheSwgTW9udGgsIFllYXIsIE51bUNhc2VzKQ0KDQpgYGANCg0KYGBge3J9DQphdV9jYXNlcw0KYGBgDQoNCg0KIyBEdXBsaWNhdGVkIGVudHJpZXMgYW5kIG5vbi10aWR5IGRhdGENCg0KVG8gY29uZm9ybSB0byB0aGUgJ3RpZHknIHByaW5jaXBsZXMgb2YgZGF0YSBvcmdhbmlzYXRpb24gd2UnbGwgc29tZXRpbWVzIGVuZCB1cCBpbnRyb2R1Y2luZyBzb21lIGR1cGxpY2F0aW9uIG9mIHZhbHVlcyB3aXRoaW4gYSBjb2x1bW4sIGUuZy4sIHRoZSBgU3RhdGVgIGNvbHVtbiBpbiB0aGUgYWJvdmUgZXhhbXBsZXMuDQoNClRoZSBvcmlnaW5hbCB3aWRlIGRhdGEgZnJhbWUgaGFkIGByIE5ST1coYXVfY2FzZXNfd2lkZSlgICRcdGltZXMkIGByIE5DT0woYXVfY2FzZXNfd2lkZSlgID0gYHIgTlJPVyhhdV9jYXNlc193aWRlKSogTkNPTChhdV9jYXNlc193aWRlKWAgZW50cmllcyBjb21wYXJlZCB3aXRoIGByIE5ST1coYXVfY2FzZXNfdGlkeSlgICRcdGltZXMkIGByIE5DT0woYXVfY2FzZXNfdGlkeSlgID0gYHIgTlJPVyhhdV9jYXNlc190aWR5KSogTkNPTChhdV9jYXNlc190aWR5KWAgZW50cmllcyBpbiB0aGUgdGlkaWVkIGRhdGEgZnJhbWUuDQoNClRoaXMgaXMgYSBwb3RlbnRpYWwgZG93bnNpZGUgZm9yIGFkb3B0aW5nIHRpZHkgZGF0YSBmb3JtYXRzIGFuZCBpdCByZW1pbmRzIHVzIHRoYXQgbm9uLXRpZHkgZGF0YSBpc24ndCBuZWNlc3NhcmlseSB0byBiZSBhdm9pZGVkIGFzIHNvbWV0aGluZyBpbmhlcmVudGx5IGRlZmljaWVudC4gRGF0YSBvZnRlbiBjb21lcyB0byB1cyBvcmdhbmlzZWQgaW4gYSBwYXJ0aWN1bGFyIHN0cnVjdHVyZSBiZWNhdXNlIGl0IG1ha2VzIG1vc3Qgc2Vuc2UgdG8gYSBodW1hbiByZWFkZXIgb3IgdGhlIGRhdGEgY29sbGVjdGlvbiBwcm9jZXNzLg0KDQpUaGUgYHRpZHlyYCBwYWNrYWdlIGlzIGRlc2lnbmVkIHRvIGFzc2lzdCB3aXRoIHdyYW5nbGluZyBzdWNoIGRhdGEgaW50byBhIGNvbnNpc3RlbnQgcmVwcmVzZW50YXRpb24gdGhhdCBjYW4gbGV2ZXJhZ2UgdGhlIHZlY3RvcmlzYXRpb24gZmVhdHVyZXMgaW4gUiBtb3JlIGJyb2FkbHkuDQoNCiMgUmVzb3VyY2VzDQoNCi0gW1IgZm9yIERhdGEgU2NpZW5jZV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei90aWR5LWRhdGEuaHRtbCksIFdpY2toYW0gJiBHcm9sZW11bmQuDQotIFtJbnRyb2R1Y3Rpb24gdG8gUiAodGlkeXZlcnNlKV0oaHR0cHM6Ly9ib29rZG93bi5vcmcvYW5zZWxsYnIvV0VISV90aWR5Ul9jb3Vyc2VfYm9vay9yZWFkaW5nLXRpZHlpbmctam9pbmluZy1kYXRhLmh0bWwpLCBXRUhJLg0KLSBbRGF0YWZyYW1lIE1hbmlwdWxhdGlvbiB3aXRoIHRpZHlyXShodHRwczovL3N3Y2FycGVudHJ5LmdpdGh1Yi5pby9yLW5vdmljZS1nYXBtaW5kZXIvMTQtdGlkeXIvKSwgVGhlIENhcnBlbnRyaWVzLg0KDQo=