Remember, before you can use the tidyverse, you need to load the package.

library(tidyverse)

Tidy Data

Note that the sample tables used in the presentation can be accessed once the tidyverse is imported by using table1, table2, table3, table4a, and table4b.

Understanding Tidy-ness

  1. (Taken form R4DS) Using prose, describe to your partner how the variables and observations are organised in each of the sample tables
  2. Which of the following representations of the ChickWeight dataset is tidy? Why are the others not?
  1. You want to create a line plot showing how mobile phone usage has changed over time in the seven main geographic regions. Which of the following tables is this easiest to do with?
  1. Which of the above forms is best if you just wish to view the raw data?

Spreading and Gathering

(Taken form R4DS)

  1. Why are gather() and spread() not perfectly symmetrical? Carefully consider the following example:
stocks <- tibble(
  year = c(2015, 2015, 2016, 2016),
  half = c(1, 2, 1, 2),
  return = c(1.88, 0.59, 0.92, 0.17)
)
stocks %>%
  spread(year, return) %>%
  gather("year", "return", `2015`, `2016`)

(Hint: look at the variable types and think about column names.)

  1. Both spread() and gather() have a convert argument. What does it do?

  2. Why does this code fail?

table4a %>%
  gather(1999, 2000, key = "year", value = "cases")
#> Error in eval(expr, envir, enclos):
#> Position must be between 0 and n
  1. Why does spreading this tibble fail? How could you add a new column to fix the problem? Look at the help page for distinct(). Could this offer an alternative solution?
people <- tribble(
  ~name,             ~key,    ~value,
  #-----------------|--------|------
  "Phillip Woods",   "age",       45,
  "Phillip Woods",   "height",   186,
  "Phillip Woods",   "age",       50,
  "Jessica Cordero", "age",       37,
  "Jessica Cordero", "height",   156
)
  1. Tidy this simple tibble. Do you need to spread or gather it? What are the variables?
preg <- tribble(
  ~pregnant, ~male, ~female,
  "yes",     NA,    10,
  "no",      20,    12
)

Separating and Uniting

(Taken form R4DS)

  1. What do the extra and fill arguments do in separate()? Experiment with the various options for the following two toy datasets:
tibble(x = c("a,b,c", "d,e,f,g", "h,i,j")) %>% 
  separate(x, c("one", "two", "three"))
Expected 3 pieces. Additional pieces discarded in 1 rows [2].
tibble(x = c("a,b,c", "d,e", "f,g,i")) %>% 
  separate(x, c("one", "two", "three"))
Expected 3 pieces. Missing pieces filled with `NA` in 1 rows [2].
  1. Both unite() and separate() have a remove argument. What does it do? Why would you set it to FALSE?

  2. (HARD) Compare and contrast separate() and extract(). Why are there three variations of separation (by position, by separator, and with groups), but only one unite?

A Practical Example

  1. Import the file olympics.csv from this session’s data folder
  2. Explore the dataset using summary(), head() and str()
  3. Recreate the following plot

Relational Data

Built-in Datasets

  1. Install the package nycflights13 using install.packages() and load it with library()
  2. Take a look at the columns of the planes, flights, and carrier datasets. The following diagram may help with understanding the relations.

Relations in the nycflights13 database

  1. Join the flights and airlines datasets using the shared column carrier. Only include observations that appear in the flights dataset
  2. What is the mean arrival delay for each carrier?
  3. Join the planes and flights datasets using the shared column tailnum. Only include observations that appear in both datasets
  4. What is the shortest length of flight for plane model?
  5. Which join should we use to fill in the blank below? The goal is to find the total number of sales for each current employee and see how this correlates with years of experience.
current_employees <- tibble(name = c('Ann', 'Brian', 'Dan', 'Elsa'),
                            years_experience = c(2.5, 4, 1.5, 0))
sales <- tibble(value = c(7, 5, 8, 4, 9, 4, 8, 5, 6, 7, 2, 5, 6, 2),
                name = c('Brian', 'Ann', 'Brian', 'Dan', 'Brian', 'Cat', 'Brian',
                         'Ann', 'Ann', 'Dan', 'Dan', 'Cat', 'Ann', 'Dan'))

sales %>%
  {???}_join(current_employees, by = 'name') %>%
  group_by(name, years_experience) %>%
  summarise(total_sales = sum(value)) %>%
  mutate(total_sales = ifelse(is.na(total_sales), 0, total_sales)) %>%
  ggplot(aes(x = years_experience, y = total_sales)) +
    geom_point() +
    geom_smooth(method = 'lm')

Going Beyond

Missing Values

  1. What does the complete() function do? When might you want to use it?
  2. What does the fill() function do? When might you want to use it?
  3. How do the fill arguments for spread() and complete() differ?
  4. What does the direction argument to fill() do?

Filtering Joins

  1. Read the documentation for semi_join() and anti_join()
  2. Using the flights and planes datasets imported above, filter planes to only show planes that have flown at least 400 times
LS0tDQp0aXRsZTogIkludG8gdGhlIFRpZHl2ZXJzZSINCnN1YnRpdGxlOiAiU2Vzc2lvbiBGb3VyIEV4ZXJjaXNlcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNClJlbWVtYmVyLCBiZWZvcmUgeW91IGNhbiB1c2UgdGhlIHRpZHl2ZXJzZSwgeW91IG5lZWQgdG8gbG9hZCB0aGUgcGFja2FnZS4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNCiMjIFRpZHkgRGF0YQ0KDQpOb3RlIHRoYXQgdGhlIHNhbXBsZSB0YWJsZXMgdXNlZCBpbiB0aGUgcHJlc2VudGF0aW9uIGNhbiBiZSBhY2Nlc3NlZCBvbmNlIHRoZSB0aWR5dmVyc2UgaXMgaW1wb3J0ZWQgYnkgdXNpbmcgYHRhYmxlMWAsIGB0YWJsZTJgLCBgdGFibGUzYCwgYHRhYmxlNGFgLCBhbmQgYHRhYmxlNGJgLg0KDQojIyMjIFVuZGVyc3RhbmRpbmcgVGlkeS1uZXNzDQoNCjEuICgqKlRha2VuIGZvcm0gUjREUyoqKSBVc2luZyBwcm9zZSwgZGVzY3JpYmUgdG8geW91ciBwYXJ0bmVyIGhvdyB0aGUgdmFyaWFibGVzIGFuZCBvYnNlcnZhdGlvbnMgYXJlIG9yZ2FuaXNlZCBpbiBlYWNoIG9mIHRoZSBzYW1wbGUgdGFibGVzDQoyLiBXaGljaCBvZiB0aGUgZm9sbG93aW5nIHJlcHJlc2VudGF0aW9ucyBvZiB0aGUgYENoaWNrV2VpZ2h0YCBkYXRhc2V0IGlzIHRpZHk/IFdoeSBhcmUgdGhlIG90aGVycyBub3Q/DQoNCmBgYHtyIGVjaG89RkFMU0V9DQpDaGlja1dlaWdodCAlPiUNCiAgYXNfdGliYmxlKCkgJT4lDQogIHNwcmVhZChrZXkgPSAnVGltZScsIHZhbHVlID0gJ3dlaWdodCcpICU+JQ0KICByZW5hbWVfYXQoMzoxNCwgfnBhc3RlMCgnRGF5ICcsIC4pKSAlPiUNCiAgaGVhZCgpDQpgYGANCg0KYGBge3IgZWNobz1GQUxTRX0NCkNoaWNrV2VpZ2h0ICU+JQ0KICBhc190aWJibGUoKSAlPiUNCiAgaGVhZCgpDQpgYGANCg0KYGBge3IgZWNobz1GQUxTRX0NCkNoaWNrV2VpZ2h0ICU+JQ0KICBhc190aWJibGUoKSAlPiUNCiAgdW5pdGUoIldlaWdodC9UaW1lIiwgd2VpZ2h0LCBUaW1lLCBzZXAgPSAnLycpICU+JQ0KICBoZWFkKCkNCmBgYA0KDQozLiBZb3Ugd2FudCB0byBjcmVhdGUgYSBsaW5lIHBsb3Qgc2hvd2luZyBob3cgbW9iaWxlIHBob25lIHVzYWdlIGhhcyBjaGFuZ2VkIG92ZXIgdGltZSBpbiB0aGUgc2V2ZW4gbWFpbiBnZW9ncmFwaGljIHJlZ2lvbnMuIFdoaWNoIG9mIHRoZSBmb2xsb3dpbmcgdGFibGVzIGlzIHRoaXMgZWFzaWVzdCB0byBkbyB3aXRoPw0KDQpgYGB7ciBlY2hvPUZBTFNFfQ0KV29ybGRQaG9uZXMgJT4lIA0KICBhc190aWJibGUoKSAlPiUNCiAgbXV0YXRlKFllYXIgPSBjKDE5NTEsIDE5NTY6MTk2MSkpICU+JQ0KICBzZWxlY3QoWWVhciwgZXZlcnl0aGluZygpKQ0KYGBgDQoNCmBgYHtyIGVjaG89RkFMU0V9DQpXb3JsZFBob25lcyAlPiUgDQogIGFzX3RpYmJsZSgpICU+JQ0KICBtdXRhdGUoWWVhciA9IGMoMTk1MSwgMTk1NjoxOTYxKSkgJT4lDQogIHNlbGVjdChZZWFyLCBldmVyeXRoaW5nKCkpICU+JQ0KICBnYXRoZXIoa2V5ID0gJ0NvdW50cnknLCB2YWx1ZSA9ICdOdW1iZXIgb2YgUGhvbmVzJywgTi5BbWVyOk1pZC5BbWVyKSAlPiUNCiAgaGVhZChuID0gMTApDQpgYGANCg0KNC4gV2hpY2ggb2YgdGhlIGFib3ZlIGZvcm1zIGlzIGJlc3QgaWYgeW91IGp1c3Qgd2lzaCB0byB2aWV3IHRoZSByYXcgZGF0YT8NCg0KIyMjIFNwcmVhZGluZyBhbmQgR2F0aGVyaW5nDQoNCigqKlRha2VuIGZvcm0gUjREUyoqKQ0KDQoxLiBXaHkgYXJlIGBnYXRoZXIoKWAgYW5kIGBzcHJlYWQoKWAgbm90IHBlcmZlY3RseSBzeW1tZXRyaWNhbD8gQ2FyZWZ1bGx5IGNvbnNpZGVyIHRoZSBmb2xsb3dpbmcgZXhhbXBsZToNCg0KYGBge3J9DQpzdG9ja3MgPC0gdGliYmxlKA0KICB5ZWFyID0gYygyMDE1LCAyMDE1LCAyMDE2LCAyMDE2KSwNCiAgaGFsZiA9IGMoMSwgMiwgMSwgMiksDQogIHJldHVybiA9IGMoMS44OCwgMC41OSwgMC45MiwgMC4xNykNCikNCnN0b2NrcyAlPiUNCiAgc3ByZWFkKHllYXIsIHJldHVybikgJT4lDQogIGdhdGhlcigieWVhciIsICJyZXR1cm4iLCBgMjAxNWAsIGAyMDE2YCkNCmBgYA0KDQooSGludDogbG9vayBhdCB0aGUgdmFyaWFibGUgdHlwZXMgYW5kIHRoaW5rIGFib3V0IGNvbHVtbiBfbmFtZXNfLikNCg0KMi4gQm90aCBgc3ByZWFkKClgIGFuZCBgZ2F0aGVyKClgIGhhdmUgYSBgY29udmVydGAgYXJndW1lbnQuIFdoYXQgZG9lcyBpdCBkbz8NCg0KMy4gV2h5IGRvZXMgdGhpcyBjb2RlIGZhaWw/DQoNCmBgYHtyIGV2YWw9RkFMU0V9DQp0YWJsZTRhICU+JQ0KICBnYXRoZXIoMTk5OSwgMjAwMCwga2V5ID0gInllYXIiLCB2YWx1ZSA9ICJjYXNlcyIpDQojPiBFcnJvciBpbiBldmFsKGV4cHIsIGVudmlyLCBlbmNsb3MpOg0KIz4gUG9zaXRpb24gbXVzdCBiZSBiZXR3ZWVuIDAgYW5kIG4NCmBgYA0KDQo0LiBXaHkgZG9lcyBzcHJlYWRpbmcgdGhpcyB0aWJibGUgZmFpbD8gSG93IGNvdWxkIHlvdSBhZGQgYSBuZXcgY29sdW1uIHRvIGZpeCB0aGUgcHJvYmxlbT8gTG9vayBhdCB0aGUgaGVscCBwYWdlIGZvciBgZGlzdGluY3QoKWAuIENvdWxkIHRoaXMgb2ZmZXIgYW4gYWx0ZXJuYXRpdmUgc29sdXRpb24/DQoNCmBgYHtyfQ0KcGVvcGxlIDwtIHRyaWJibGUoDQogIH5uYW1lLCAgICAgICAgICAgICB+a2V5LCAgICB+dmFsdWUsDQogICMtLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLXwtLS0tLS0NCiAgIlBoaWxsaXAgV29vZHMiLCAgICJhZ2UiLCAgICAgICA0NSwNCiAgIlBoaWxsaXAgV29vZHMiLCAgICJoZWlnaHQiLCAgIDE4NiwNCiAgIlBoaWxsaXAgV29vZHMiLCAgICJhZ2UiLCAgICAgICA1MCwNCiAgIkplc3NpY2EgQ29yZGVybyIsICJhZ2UiLCAgICAgICAzNywNCiAgIkplc3NpY2EgQ29yZGVybyIsICJoZWlnaHQiLCAgIDE1Ng0KKQ0KYGBgDQoNCjUuIFRpZHkgdGhpcyBzaW1wbGUgdGliYmxlLiBEbyB5b3UgbmVlZCB0byBzcHJlYWQgb3IgZ2F0aGVyIGl0PyBXaGF0IGFyZSB0aGUgdmFyaWFibGVzPw0KDQpgYGB7cn0NCnByZWcgPC0gdHJpYmJsZSgNCiAgfnByZWduYW50LCB+bWFsZSwgfmZlbWFsZSwNCiAgInllcyIsICAgICBOQSwgICAgMTAsDQogICJubyIsICAgICAgMjAsICAgIDEyDQopDQpgYGANCg0KIyMjIFNlcGFyYXRpbmcgYW5kIFVuaXRpbmcNCg0KKCoqVGFrZW4gZm9ybSBSNERTKiopDQoNCjEuIFdoYXQgZG8gdGhlIGBleHRyYWAgYW5kIGBmaWxsYCBhcmd1bWVudHMgZG8gaW4gYHNlcGFyYXRlKClgPyBFeHBlcmltZW50IHdpdGggdGhlIHZhcmlvdXMgb3B0aW9ucyBmb3IgdGhlIGZvbGxvd2luZyB0d28gdG95IGRhdGFzZXRzOg0KDQpgYGB7cn0NCnRpYmJsZSh4ID0gYygiYSxiLGMiLCAiZCxlLGYsZyIsICJoLGksaiIpKSAlPiUgDQogIHNlcGFyYXRlKHgsIGMoIm9uZSIsICJ0d28iLCAidGhyZWUiKSkNCg0KdGliYmxlKHggPSBjKCJhLGIsYyIsICJkLGUiLCAiZixnLGkiKSkgJT4lIA0KICBzZXBhcmF0ZSh4LCBjKCJvbmUiLCAidHdvIiwgInRocmVlIikpDQpgYGANCg0KMi4gQm90aCBgdW5pdGUoKWAgYW5kIGBzZXBhcmF0ZSgpYCBoYXZlIGEgYHJlbW92ZWAgYXJndW1lbnQuIFdoYXQgZG9lcyBpdCBkbz8gV2h5IHdvdWxkIHlvdSBzZXQgaXQgdG8gYEZBTFNFYD8NCg0KMy4gKCoqSEFSRCoqKSBDb21wYXJlIGFuZCBjb250cmFzdCBgc2VwYXJhdGUoKWAgYW5kIGBleHRyYWN0KClgLiBXaHkgYXJlIHRoZXJlIHRocmVlIHZhcmlhdGlvbnMgb2Ygc2VwYXJhdGlvbiAoYnkgcG9zaXRpb24sIGJ5IHNlcGFyYXRvciwgYW5kIHdpdGggZ3JvdXBzKSwgYnV0IG9ubHkgb25lIHVuaXRlPw0KDQojIyMgQSBQcmFjdGljYWwgRXhhbXBsZQ0KDQoxLiBJbXBvcnQgdGhlIGZpbGUgYG9seW1waWNzLmNzdmAgZnJvbSB0aGlzIHNlc3Npb24ncyBkYXRhIGZvbGRlcg0KMi4gRXhwbG9yZSB0aGUgZGF0YXNldCB1c2luZyBgc3VtbWFyeSgpYCwgYGhlYWQoKWAgYW5kIGBzdHIoKWANCjMuIFJlY3JlYXRlIHRoZSBmb2xsb3dpbmcgcGxvdA0KDQpgYGB7ciBlY2hvPUZBTFNFfQ0KcmVhZF9jc3YoImRhdGEvb2x5bXBpY3MuY3N2IiwgY29sX3R5cGVzID0gY29scygpKSAlPiUNCiAgZ3JvdXBfYnkoVGVhbSwgWWVhciwgTWVkYWwpICU+JQ0KICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpICU+JQ0KICBzcHJlYWQoTWVkYWwsIENvdW50KSAlPiUNCiAgbXV0YXRlKFRvdGFsID0gc3VtKEJyb256ZSwgU2lsdmVyLCBHb2xkLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gWWVhciwgeSA9IFRvdGFsLCBjb2wgPSBUZWFtKSkgKw0KICAgIGdlb21fbGluZShzaXplID0gMikgKw0KICAgIHRoZW1lX2xpZ2h0KCkgKw0KICAgIGxhYnMoeSA9ICJUb3RhbCBOdW1iZXIgb2YgTWVkYWxzIiwNCiAgICAgICAgIHRpdGxlID0gIk9seW1waWMgTWVkYWwgQ291bnRzIGluIGEgU2VsZWN0aW9uIG9mIEVhc3Rlcm4gRXVyb3BlYW4gQ291bnRyaWVzIiwNCiAgICAgICAgIGNvbCA9ICJDb3VudHJ5IikNCmBgYA0KDQojIyBSZWxhdGlvbmFsIERhdGENCg0KIyMjIEJ1aWx0LWluIERhdGFzZXRzDQoNCjEuIEluc3RhbGwgdGhlIHBhY2thZ2UgYG55Y2ZsaWdodHMxM2AgdXNpbmcgYGluc3RhbGwucGFja2FnZXMoKWAgYW5kIGxvYWQgaXQgd2l0aCBgbGlicmFyeSgpYA0KMi4gVGFrZSBhIGxvb2sgYXQgdGhlIGNvbHVtbnMgb2YgdGhlIGBwbGFuZXNgLCBgZmxpZ2h0c2AsIGFuZCBgY2FycmllcmAgZGF0YXNldHMuIFRoZSBmb2xsb3dpbmcgZGlhZ3JhbSBtYXkgaGVscCB3aXRoIHVuZGVyc3RhbmRpbmcgdGhlIHJlbGF0aW9ucy4NCg0KIVtSZWxhdGlvbnMgaW4gdGhlIG55Y2ZsaWdodHMxMyBkYXRhYmFzZV0oaW1hZ2VzL3JlbGF0aW9uYWwtbnljZmxpZ2h0cy5wbmcpDQoNCjMuIEpvaW4gdGhlIGBmbGlnaHRzYCBhbmQgYGFpcmxpbmVzYCBkYXRhc2V0cyB1c2luZyB0aGUgc2hhcmVkIGNvbHVtbiBgY2FycmllcmAuIE9ubHkgaW5jbHVkZSBvYnNlcnZhdGlvbnMgdGhhdCBhcHBlYXIgaW4gdGhlIGBmbGlnaHRzYCBkYXRhc2V0DQo0LiBXaGF0IGlzIHRoZSBtZWFuIGFycml2YWwgZGVsYXkgZm9yIGVhY2ggY2Fycmllcj8NCjUuIEpvaW4gdGhlIGBwbGFuZXNgIGFuZCBgZmxpZ2h0c2AgZGF0YXNldHMgdXNpbmcgdGhlIHNoYXJlZCBjb2x1bW4gYHRhaWxudW1gLiBPbmx5IGluY2x1ZGUgb2JzZXJ2YXRpb25zIHRoYXQgYXBwZWFyIGluIGJvdGggZGF0YXNldHMNCjYuIFdoYXQgaXMgdGhlIHNob3J0ZXN0IGxlbmd0aCBvZiBmbGlnaHQgZm9yIHBsYW5lIG1vZGVsPw0KNy4gV2hpY2ggam9pbiBzaG91bGQgd2UgdXNlIHRvIGZpbGwgaW4gdGhlIGJsYW5rIGJlbG93PyBUaGUgZ29hbCBpcyB0byBmaW5kIHRoZSB0b3RhbCBudW1iZXIgb2Ygc2FsZXMgZm9yIGVhY2ggX2N1cnJlbnRfIGVtcGxveWVlIGFuZCBzZWUgaG93IHRoaXMgY29ycmVsYXRlcyB3aXRoIHllYXJzIG9mIGV4cGVyaWVuY2UuDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQpjdXJyZW50X2VtcGxveWVlcyA8LSB0aWJibGUobmFtZSA9IGMoJ0FubicsICdCcmlhbicsICdEYW4nLCAnRWxzYScpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHllYXJzX2V4cGVyaWVuY2UgPSBjKDIuNSwgNCwgMS41LCAwKSkNCnNhbGVzIDwtIHRpYmJsZSh2YWx1ZSA9IGMoNywgNSwgOCwgNCwgOSwgNCwgOCwgNSwgNiwgNywgMiwgNSwgNiwgMiksDQogICAgICAgICAgICAgICAgbmFtZSA9IGMoJ0JyaWFuJywgJ0FubicsICdCcmlhbicsICdEYW4nLCAnQnJpYW4nLCAnQ2F0JywgJ0JyaWFuJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAnQW5uJywgJ0FubicsICdEYW4nLCAnRGFuJywgJ0NhdCcsICdBbm4nLCAnRGFuJykpDQoNCnNhbGVzICU+JQ0KICB7Pz8/fV9qb2luKGN1cnJlbnRfZW1wbG95ZWVzLCBieSA9ICduYW1lJykgJT4lDQogIGdyb3VwX2J5KG5hbWUsIHllYXJzX2V4cGVyaWVuY2UpICU+JQ0KICBzdW1tYXJpc2UodG90YWxfc2FsZXMgPSBzdW0odmFsdWUpKSAlPiUNCiAgbXV0YXRlKHRvdGFsX3NhbGVzID0gaWZlbHNlKGlzLm5hKHRvdGFsX3NhbGVzKSwgMCwgdG90YWxfc2FsZXMpKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0geWVhcnNfZXhwZXJpZW5jZSwgeSA9IHRvdGFsX3NhbGVzKSkgKw0KICAgIGdlb21fcG9pbnQoKSArDQogICAgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJykNCmBgYA0KDQojIyBHb2luZyBCZXlvbmQNCg0KIyMjIE1pc3NpbmcgVmFsdWVzDQoNCjEuIFdoYXQgZG9lcyB0aGUgYGNvbXBsZXRlKClgIGZ1bmN0aW9uIGRvPyBXaGVuIG1pZ2h0IHlvdSB3YW50IHRvIHVzZSBpdD8NCjIuIFdoYXQgZG9lcyB0aGUgYGZpbGwoKWAgZnVuY3Rpb24gZG8/IFdoZW4gbWlnaHQgeW91IHdhbnQgdG8gdXNlIGl0Pw0KMy4gSG93IGRvIHRoZSBgZmlsbGAgYXJndW1lbnRzIGZvciBgc3ByZWFkKClgIGFuZCBgY29tcGxldGUoKWAgZGlmZmVyPw0KNC4gV2hhdCBkb2VzIHRoZSBkaXJlY3Rpb24gYXJndW1lbnQgdG8gYGZpbGwoKWAgZG8/DQoNCiMjIyBGaWx0ZXJpbmcgSm9pbnMNCg0KMS4gUmVhZCB0aGUgZG9jdW1lbnRhdGlvbiBmb3IgYHNlbWlfam9pbigpYCBhbmQgYGFudGlfam9pbigpYA0KMi4gVXNpbmcgdGhlIGBmbGlnaHRzYCBhbmQgYHBsYW5lc2AgZGF0YXNldHMgaW1wb3J0ZWQgYWJvdmUsIGZpbHRlciBgcGxhbmVzYCB0byBvbmx5IHNob3cgcGxhbmVzIHRoYXQgaGF2ZSBmbG93biBhdCBsZWFzdCA0MDAgdGltZXM=