Categorical Data in R

1 Introduction

Let us play with some categorical ( or predominantly categorical ) datasets in R and see how we can analyze and plot them.

First we will learn how to make Contingency Tables with any of the three forms. This will be useful in arriving at a common form of Table to go with plotting.

Then we will use vcd, mosaic, ggplot and ggpubr to make several plots for Categorical Datasets.

knitr::opts_chunk$set(message = FALSE, echo = TRUE)
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.2 ──
## ✔ ggplot2 3.4.0      ✔ purrr   1.0.1 
## ✔ tibble  3.1.8      ✔ dplyr   1.0.10
## ✔ tidyr   1.2.1      ✔ stringr 1.5.0 
## ✔ readr   2.1.3      ✔ forcats 0.5.2 
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
library(vcd) #COntingency Tables and Plots
## Loading required package: grid
library(vcdExtra) # Datasets
## Loading required package: gnm
## 
## Attaching package: 'vcdExtra'
## 
## The following object is masked from 'package:dplyr':
## 
##     summarise
library(sjPlot) # Likert Plots
## Install package "strengejacke" from GitHub (`devtools::install_github("strengejacke/strengejacke")`) to load all sj-packages at once!
library(mosaic) # Data Analysis and Plots
## Registered S3 method overwritten by 'mosaic':
##   method                           from   
##   fortify.SpatialPolygonsDataFrame ggplot2
## 
## The 'mosaic' package masks several functions from core packages in order to add 
## additional features.  The original behavior of these functions should not be affected by this.
## 
## Attaching package: 'mosaic'
## 
## The following object is masked from 'package:Matrix':
## 
##     mean
## 
## The following object is masked from 'package:vcd':
## 
##     mplot
## 
## The following objects are masked from 'package:dplyr':
## 
##     count, do, tally
## 
## The following object is masked from 'package:purrr':
## 
##     cross
## 
## The following object is masked from 'package:ggplot2':
## 
##     stat
## 
## The following objects are masked from 'package:stats':
## 
##     binom.test, cor, cor.test, cov, fivenum, IQR, median, prop.test,
##     quantile, sd, t.test, var
## 
## The following objects are masked from 'package:base':
## 
##     max, mean, min, prod, range, sample, sum
library(ggmosaic) # Mosaic Plots
## 
## Attaching package: 'ggmosaic'
## 
## The following objects are masked from 'package:vcd':
## 
##     mosaic, spine
library(ggpubr) # Balloon Plots

#install.packages("openintro")
library(openintro) # More Datasets
## Loading required package: airports
## Loading required package: cherryblossom
## Loading required package: usdata
## 
## Attaching package: 'openintro'
## 
## The following object is masked from 'package:mosaic':
## 
##     dotPlot
## 
## The following objects are masked from 'package:lattice':
## 
##     ethanol, lsegments

2 Creating Contingency Tables

Most plots for Categorical Data ( as we shall see ) require that the data be converted into a Contingency Table; even Statistical tests for Proportions ( the $ X^2 $ test ) need Contingency Tables. The Frequency Table we encountered earlier is very close to being a full-fledged Contingency Table. ( needs to add row and column margin counts )

In this section we understand how to make Contingency Tables from each of the three forms.

2.1 Using base R

Arthritis
#One Way Table ( one variable )
table(Arthritis$Treatment)
## 
## Placebo Treated 
##      43      41
table(Arthritis$Treatment) %>% prop.table()
## 
##   Placebo   Treated 
## 0.5119048 0.4880952
table(Arthritis$Treatment) %>% addmargins()
## 
## Placebo Treated     Sum 
##      43      41      84
# Two-Way Table ( two variables )
table(Arthritis$Treatment, Arthritis$Improved) %>% prop.table() 
##          
##                 None       Some     Marked
##   Placebo 0.34523810 0.08333333 0.08333333
##   Treated 0.15476190 0.08333333 0.25000000
table(Arthritis$Treatment, Arthritis$Improved) %>% addmargins() # Contingency Table!!
##          
##           None Some Marked Sum
##   Placebo   29    7      7  43
##   Treated   13    7     21  41
##   Sum       42   14     28  84

We can use table() and xtabs() to generate multi-dimensional tables too ( More than 2D ) These will be printed out as a series of 2D tables, one for each value of the “third” parameter.

We can also ftable() to print these out in an attractive manner.

mytable <- table(Arthritis$Treatment, Arthritis$Sex, Arthritis$Improved)
mytable
## , ,  = None
## 
##          
##           Female Male
##   Placebo     19   10
##   Treated      6    7
## 
## , ,  = Some
## 
##          
##           Female Male
##   Placebo      7    0
##   Treated      5    2
## 
## , ,  = Marked
## 
##          
##           Female Male
##   Placebo      6    1
##   Treated     16    5
ftable(mytable)
##                 None Some Marked
##                                 
## Placebo Female    19    7      6
##         Male      10    0      1
## Treated Female     6    5     16
##         Male       7    2      5
ftable(mytable) %>% addmargins()
##              Sum
##     19  7  6  32
##     10  0  1  11
##      6  5 16  27
##      7  2  5  14
## Sum 42 14 28  84

2.2 Using the vcd package

The vcd ( Visualize Categorical Data ) package by Michael Friendly has a convenient function to create Contingency Tables: structable(); this function produces a ‘flat’ representation of a high-dimensional contingency table constructed by recursive splits (similar to the construction of mosaic charts/graphs).

The arguments of structable are:

  • a formula ($y + p x + z $ ) which shows which variables are to be included as columns and rows respectively on a table
  • a data argument, which can indicate a data frame
vcd::structable(data = Arthritis, Treatment ~ Improved)
##          Treatment Placebo Treated
## Improved                          
## None                    29      13
## Some                     7       7
## Marked                   7      21
vcd::structable(data = Arthritis, Treatment ~ Improved + Sex)
##                 Treatment Placebo Treated
## Improved Sex                             
## None     Female                19       6
##          Male                  10       7
## Some     Female                 7       5
##          Male                   0       2
## Marked   Female                 6      16
##          Male                   1       5
# HairEyeColor is in multiple table form
# structable flattens these into one, as for a mosaic chart
HairEyeColor
## , , Sex = Male
## 
##        Eye
## Hair    Brown Blue Hazel Green
##   Black    32   11    10     3
##   Brown    53   50    25    15
##   Red      10   10     7     7
##   Blond     3   30     5     8
## 
## , , Sex = Female
## 
##        Eye
## Hair    Brown Blue Hazel Green
##   Black    36    9     5     2
##   Brown    66   34    29    14
##   Red      16    7     7     7
##   Blond     4   64     5     8
vcd::structable(HairEyeColor)
##              Eye Brown Blue Hazel Green
## Hair  Sex                              
## Black Male          32   11    10     3
##       Female        36    9     5     2
## Brown Male          53   50    25    15
##       Female        66   34    29    14
## Red   Male          10   10     7     7
##       Female        16    7     7     7
## Blond Male           3   30     5     8
##       Female         4   64     5     8
## UCBAdmissions is already in Frequency Form i.e. a Contingency Table
#`structable` tends to render flat tables, of the kind that can be thought of as a "text representation" of the `vcd::mosaic` plot
UCBAdmissions
## , , Dept = A
## 
##           Gender
## Admit      Male Female
##   Admitted  512     89
##   Rejected  313     19
## 
## , , Dept = B
## 
##           Gender
## Admit      Male Female
##   Admitted  353     17
##   Rejected  207      8
## 
## , , Dept = C
## 
##           Gender
## Admit      Male Female
##   Admitted  120    202
##   Rejected  205    391
## 
## , , Dept = D
## 
##           Gender
## Admit      Male Female
##   Admitted  138    131
##   Rejected  279    244
## 
## , , Dept = E
## 
##           Gender
## Admit      Male Female
##   Admitted   53     94
##   Rejected  138    299
## 
## , , Dept = F
## 
##           Gender
## Admit      Male Female
##   Admitted   22     24
##   Rejected  351    317
vcd::structable(UCBAdmissions)
##               Gender Male Female
## Admit    Dept                   
## Admitted A            512     89
##          B            353     17
##          C            120    202
##          D            138    131
##          E             53     94
##          F             22     24
## Rejected A            313     19
##          B            207      8
##          C            205    391
##          D            279    244
##          E            138    299
##          F            351    317

2.3 Using the mosaic package

I think this is the simplest and most elegant way of obtaining Contingency Tables:

# One Way Table
tally( ~ substance, data = HELPrct)
## substance
## alcohol cocaine  heroin 
##     177     152     124
# Two Way Tables
tally( ~ substance + sex , data = HELPrct)
##          sex
## substance female male
##   alcohol     36  141
##   cocaine     41  111
##   heroin      30   94
tally( ~ substance | sex , data = HELPrct)
##          sex
## substance female male
##   alcohol     36  141
##   cocaine     41  111
##   heroin      30   94
tally( sex ~ substance, data = HELPrct)
##         substance
## sex      alcohol cocaine heroin
##   female      36      41     30
##   male       141     111     94
tally(~ sex |substance, data = HELPrct)
##         substance
## sex      alcohol cocaine heroin
##   female      36      41     30
##   male       141     111     94
# Adding Margins
tally( ~ substance + sex , data = HELPrct, format = 'count', margins = TRUE) # Ta Da!
##          sex
## substance female male Total
##   alcohol     36  141   177
##   cocaine     41  111   152
##   heroin      30   94   124
##   Total      107  346   453
tally( ~ substance + sex , data = HELPrct, format = 'percent', margins = TRUE)
##          sex
## substance     female       male      Total
##   alcohol   7.947020  31.125828  39.072848
##   cocaine   9.050773  24.503311  33.554084
##   heroin    6.622517  20.750552  27.373068
##   Total    23.620309  76.379691 100.000000

2.3.1 Using the tidyverse

diamonds %>% group_by(cut, clarity) %>% dplyr::summarise( count = n())
# We need to pivot this "wide" to obtain a Contingency Table

diamonds %>% 
  group_by(cut, clarity) %>% 
  dplyr::summarise( count = n()) %>% 
  pivot_wider(id_cols = cut, names_from = clarity, values_from = count) %>% 
  
  # Now add the row and column totals using the `janitor` package
  janitor::adorn_totals(where = c("row", "col")) %>%
  
  # Recovert to tibble since janitor gives a "tabyl" format ( which can be useful )
  as_tibble()

Now that we have Contingency Tables, we can plot these:

3 Plotting Categorical Data

3.1 The titanic dataset

data("titanic")
titanic

3.1.1 titanic Bar Plots

# Use dplyr and ggplot

3.1.2 titanic Mosaic Plot

# Try the mosaic package and the ggmosaic package

3.1.3 titanic Balloon Plot

# use ggpubr

3.2 The hippocorpus dataset from Kaggle

This is a dataset from Kaggle and is based on Reference 2.

Hippocorpus is dataset of 6854 English diary like short stories about recalled and imagined events. Using a crowdsourcing framework the respective owners of this datasets collected recalled stories and summaries from workers, then provided these collected summaries to other workers who write imagined stories. Months later dataset creators collected a retold version of the recalled stories from the subset of recalled authors. Dataset contains author demographics (age, gender, race), their openness to experience, as well as some variables regarding the author’s relationship to the event (e.g., how personal the event is, how often they tell its story, etc.)

Apart from metadata pertaining to each respondent, there are 4 Likert Scale variables:

  • distracted: How distracted were you while writing your story? (5-point Likert)
  • draining: How taxing/draining was writing for you emotionally? (5-point Likert)
  • frequency: How often do you think about or talk about this event? (5-point Likert)
  • importance: How impactful, important, or personal is this story/this event to you? (5-point Likert). Plot these using the package sjPlot. Can you also try a ggplot?

3.3 A dataset from the vcdExtra package

Pick one of the fairly large Categorical datasets that are built into vcdExtra: type data(package = "vcdExtra") in your Console.

Create:
- Contingency Table - A Bar Plot - A Mosaic Plot - A Balloon Plot

4 Conclusion

Write a few comments on the data and visualizations. Did they convey a story of sorts?

5 References

  1. A detailed analysis of the NHANES dataset, https://awagaman.people.amherst.edu/stat230/Stat230CodeCompilationExampleCodeUsingNHANES.pdf

  2. Maarten Sap, Eric Horvitz, Yejin Choi, Noah A. Smith, and James Pennebaker (2020) Recollection versus Imagination: Exploring Human Memory and Cognition via Neural Language Models. ACL.

LS0tDQp0aXRsZTogIkNhdGVnb3JpY2FsIERhdGEgaW4gUiINCmF1dGhvcjogIkFydmluZCBWZW5rYXRhZHJpIg0KZGF0ZTogMjAyMy8xNi8wMQ0KbGFzdG1vZDogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBybWRmb3JtYXRzOjpyZWFkdGhlZG93bjoNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgdG9jX2RlcHRoOiAzDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgbnVtYmVyX3NlY3Rpb25zOiBUUlVFDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KZWRpdG9yX29wdGlvbnM6IA0KICBtYXJrZG93bjogDQogICAgd3JhcDogNzINCi0tLQ0KDQojIEludHJvZHVjdGlvbg0KDQpMZXQgdXMgcGxheSB3aXRoIHNvbWUgY2F0ZWdvcmljYWwgKCBvciBwcmVkb21pbmFudGx5IGNhdGVnb3JpY2FsICkNCmRhdGFzZXRzIGluIFIgYW5kIHNlZSBob3cgd2UgY2FuIGFuYWx5emUgYW5kIHBsb3QgdGhlbS4NCg0KRmlyc3Qgd2Ugd2lsbCBsZWFybiBob3cgdG8gbWFrZSBDb250aW5nZW5jeSBUYWJsZXMgd2l0aCBhbnkgb2YgdGhlIHRocmVlIGZvcm1zLiBUaGlzIHdpbGwgYmUgdXNlZnVsIGluIGFycml2aW5nIGF0IGEgY29tbW9uIGZvcm0gb2YgVGFibGUgdG8gZ28gd2l0aCBwbG90dGluZy4NCg0KVGhlbiB3ZSB3aWxsIHVzZSBgdmNkYCwgYG1vc2FpY2AsIGBnZ3Bsb3RgIGFuZCBgZ2dwdWJyYCB0byBtYWtlIHNldmVyYWwgcGxvdHMgZm9yIENhdGVnb3JpY2FsIERhdGFzZXRzLiANCg0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1UUlVFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KG1lc3NhZ2UgPSBGQUxTRSwgZWNobyA9IFRSVUUpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkodmNkKSAjQ09udGluZ2VuY3kgVGFibGVzIGFuZCBQbG90cw0KbGlicmFyeSh2Y2RFeHRyYSkgIyBEYXRhc2V0cw0KbGlicmFyeShzalBsb3QpICMgTGlrZXJ0IFBsb3RzDQpsaWJyYXJ5KG1vc2FpYykgIyBEYXRhIEFuYWx5c2lzIGFuZCBQbG90cw0KbGlicmFyeShnZ21vc2FpYykgIyBNb3NhaWMgUGxvdHMNCmxpYnJhcnkoZ2dwdWJyKSAjIEJhbGxvb24gUGxvdHMNCg0KI2luc3RhbGwucGFja2FnZXMoIm9wZW5pbnRybyIpDQpsaWJyYXJ5KG9wZW5pbnRybykgIyBNb3JlIERhdGFzZXRzDQoNCmBgYA0KDQoNCiMgQ3JlYXRpbmcgQ29udGluZ2VuY3kgVGFibGVzIHsudGFic2V0IC50YWJzZXQtcGlsbHN9DQoNCk1vc3QgcGxvdHMgZm9yIENhdGVnb3JpY2FsIERhdGEgKCBhcyB3ZSBzaGFsbCBzZWUgKSByZXF1aXJlIHRoYXQgdGhlIGRhdGEgYmUgY29udmVydGVkIGludG8gYSAqQ29udGluZ2VuY3kgVGFibGUqOyBldmVuIFN0YXRpc3RpY2FsIHRlc3RzIGZvciBQcm9wb3J0aW9ucyAoIHRoZSAkIFheMiAkIHRlc3QgKSBuZWVkIENvbnRpbmdlbmN5IFRhYmxlcy4gVGhlICpGcmVxdWVuY3kgVGFibGUqIHdlIGVuY291bnRlcmVkIGVhcmxpZXIgaXMgdmVyeSBjbG9zZSB0byBiZWluZyBhIGZ1bGwtZmxlZGdlZCBDb250aW5nZW5jeSBUYWJsZS4gKCBuZWVkcyB0byBhZGQgcm93IGFuZCBjb2x1bW4gbWFyZ2luIGNvdW50cyApDQoNCkluIHRoaXMgc2VjdGlvbiB3ZSB1bmRlcnN0YW5kIGhvdyB0byBtYWtlIENvbnRpbmdlbmN5IFRhYmxlcyBmcm9tIGVhY2ggb2YgdGhlIHRocmVlIGZvcm1zLg0KDQojIyBVc2luZyBiYXNlIFINCg0KYGBge3J9DQpBcnRocml0aXMNCg0KI09uZSBXYXkgVGFibGUgKCBvbmUgdmFyaWFibGUgKQ0KdGFibGUoQXJ0aHJpdGlzJFRyZWF0bWVudCkNCnRhYmxlKEFydGhyaXRpcyRUcmVhdG1lbnQpICU+JSBwcm9wLnRhYmxlKCkNCnRhYmxlKEFydGhyaXRpcyRUcmVhdG1lbnQpICU+JSBhZGRtYXJnaW5zKCkNCmBgYA0KDQoNCmBgYHtyfQ0KDQojIFR3by1XYXkgVGFibGUgKCB0d28gdmFyaWFibGVzICkNCnRhYmxlKEFydGhyaXRpcyRUcmVhdG1lbnQsIEFydGhyaXRpcyRJbXByb3ZlZCkgJT4lIHByb3AudGFibGUoKSANCnRhYmxlKEFydGhyaXRpcyRUcmVhdG1lbnQsIEFydGhyaXRpcyRJbXByb3ZlZCkgJT4lIGFkZG1hcmdpbnMoKSAjIENvbnRpbmdlbmN5IFRhYmxlISENCg0KYGBgDQoNCldlIGNhbiB1c2UgKip0YWJsZSgpKiogYW5kICoqeHRhYnMoKSoqIHRvIGdlbmVyYXRlIG11bHRpLWRpbWVuc2lvbmFsIHRhYmxlcyB0b28gKCBNb3JlIHRoYW4gMkQgKSBUaGVzZSB3aWxsIGJlIHByaW50ZWQgb3V0IGFzIGEgc2VyaWVzIG9mIDJEIHRhYmxlcywgb25lIGZvciBlYWNoIHZhbHVlIG9mIHRoZSAidGhpcmQiIHBhcmFtZXRlci4gDQoNCldlIGNhbiBhbHNvICoqZnRhYmxlKCkqKiB0byBwcmludCB0aGVzZSBvdXQgaW4gYW4gYXR0cmFjdGl2ZSBtYW5uZXIuDQoNCmBgYHtyfQ0KDQpteXRhYmxlIDwtIHRhYmxlKEFydGhyaXRpcyRUcmVhdG1lbnQsIEFydGhyaXRpcyRTZXgsIEFydGhyaXRpcyRJbXByb3ZlZCkNCm15dGFibGUNCmZ0YWJsZShteXRhYmxlKQ0KZnRhYmxlKG15dGFibGUpICU+JSBhZGRtYXJnaW5zKCkNCg0KYGBgDQoNCg0KIyMgVXNpbmcgdGhlIGB2Y2RgIHBhY2thZ2UNCg0KVGhlIGB2Y2RgICggVmlzdWFsaXplIENhdGVnb3JpY2FsIERhdGEgKSBwYWNrYWdlIGJ5IE1pY2hhZWwgRnJpZW5kbHkgaGFzIGEgY29udmVuaWVudCBmdW5jdGlvbiB0byBjcmVhdGUgQ29udGluZ2VuY3kgVGFibGVzOiBgc3RydWN0YWJsZSgpYDsgdGhpcyBmdW5jdGlvbiBwcm9kdWNlcyBhIOKAmGZsYXTigJkgcmVwcmVzZW50YXRpb24gb2YgYSBoaWdoLWRpbWVuc2lvbmFsIGNvbnRpbmdlbmN5IHRhYmxlIGNvbnN0cnVjdGVkIGJ5IHJlY3Vyc2l2ZSBzcGxpdHMgKHNpbWlsYXIgdG8gdGhlIGNvbnN0cnVjdGlvbiBvZiBtb3NhaWMgY2hhcnRzL2dyYXBocykuDQoNClRoZSBhcmd1bWVudHMgb2YgYHN0cnVjdGFibGVgIGFyZTogIA0KDQotIGEgZm9ybXVsYSAoJHkgKyBwIFxzaW0geCArIHogJCApIHdoaWNoIHNob3dzIHdoaWNoIHZhcmlhYmxlcyBhcmUgdG8gYmUgaW5jbHVkZWQgYXMgKmNvbHVtbnMqIGFuZCAqcm93cyogcmVzcGVjdGl2ZWx5IG9uIGEgdGFibGUgIA0KLSBhIGBkYXRhYCBhcmd1bWVudCwgd2hpY2ggY2FuIGluZGljYXRlIGEgYGRhdGEgZnJhbWVgDQoNCg0KYGBge3J9DQp2Y2Q6OnN0cnVjdGFibGUoZGF0YSA9IEFydGhyaXRpcywgVHJlYXRtZW50IH4gSW1wcm92ZWQpDQp2Y2Q6OnN0cnVjdGFibGUoZGF0YSA9IEFydGhyaXRpcywgVHJlYXRtZW50IH4gSW1wcm92ZWQgKyBTZXgpDQoNCg0KIyBIYWlyRXllQ29sb3IgaXMgaW4gbXVsdGlwbGUgdGFibGUgZm9ybQ0KIyBzdHJ1Y3RhYmxlIGZsYXR0ZW5zIHRoZXNlIGludG8gb25lLCBhcyBmb3IgYSBtb3NhaWMgY2hhcnQNCkhhaXJFeWVDb2xvcg0KdmNkOjpzdHJ1Y3RhYmxlKEhhaXJFeWVDb2xvcikNCg0KIyMgVUNCQWRtaXNzaW9ucyBpcyBhbHJlYWR5IGluIEZyZXF1ZW5jeSBGb3JtIGkuZS4gYSBDb250aW5nZW5jeSBUYWJsZQ0KI2BzdHJ1Y3RhYmxlYCB0ZW5kcyB0byByZW5kZXIgZmxhdCB0YWJsZXMsIG9mIHRoZSBraW5kIHRoYXQgY2FuIGJlIHRob3VnaHQgb2YgYXMgYSAidGV4dCByZXByZXNlbnRhdGlvbiIgb2YgdGhlIGB2Y2Q6Om1vc2FpY2AgcGxvdA0KVUNCQWRtaXNzaW9ucw0KdmNkOjpzdHJ1Y3RhYmxlKFVDQkFkbWlzc2lvbnMpDQoNCmBgYA0KDQoNCiMjIFVzaW5nIHRoZSBgbW9zYWljYCBwYWNrYWdlDQoNCkkgdGhpbmsgdGhpcyBpcyB0aGUgc2ltcGxlc3QgYW5kIG1vc3QgZWxlZ2FudCB3YXkgb2Ygb2J0YWluaW5nIENvbnRpbmdlbmN5IFRhYmxlczoNCg0KYGBge3J9DQojIE9uZSBXYXkgVGFibGUNCnRhbGx5KCB+IHN1YnN0YW5jZSwgZGF0YSA9IEhFTFByY3QpDQoNCiMgVHdvIFdheSBUYWJsZXMNCnRhbGx5KCB+IHN1YnN0YW5jZSArIHNleCAsIGRhdGEgPSBIRUxQcmN0KQ0KdGFsbHkoIH4gc3Vic3RhbmNlIHwgc2V4ICwgZGF0YSA9IEhFTFByY3QpDQoNCnRhbGx5KCBzZXggfiBzdWJzdGFuY2UsIGRhdGEgPSBIRUxQcmN0KQ0KdGFsbHkofiBzZXggfHN1YnN0YW5jZSwgZGF0YSA9IEhFTFByY3QpDQoNCmBgYA0KDQoNCmBgYHtyLGhpZ2hsaWdodD1UUlVFfQ0KDQojIEFkZGluZyBNYXJnaW5zDQp0YWxseSggfiBzdWJzdGFuY2UgKyBzZXggLCBkYXRhID0gSEVMUHJjdCwgZm9ybWF0ID0gJ2NvdW50JywgbWFyZ2lucyA9IFRSVUUpICMgVGEgRGEhDQp0YWxseSggfiBzdWJzdGFuY2UgKyBzZXggLCBkYXRhID0gSEVMUHJjdCwgZm9ybWF0ID0gJ3BlcmNlbnQnLCBtYXJnaW5zID0gVFJVRSkNCg0KYGBgDQoNCiMjIyBVc2luZyB0aGUgYHRpZHl2ZXJzZWANCg0KDQpgYGB7cn0NCmRpYW1vbmRzICU+JSBncm91cF9ieShjdXQsIGNsYXJpdHkpICU+JSBkcGx5cjo6c3VtbWFyaXNlKCBjb3VudCA9IG4oKSkNCg0KIyBXZSBuZWVkIHRvIHBpdm90IHRoaXMgIndpZGUiIHRvIG9idGFpbiBhIENvbnRpbmdlbmN5IFRhYmxlDQoNCmRpYW1vbmRzICU+JSANCiAgZ3JvdXBfYnkoY3V0LCBjbGFyaXR5KSAlPiUgDQogIGRwbHlyOjpzdW1tYXJpc2UoIGNvdW50ID0gbigpKSAlPiUgDQogIHBpdm90X3dpZGVyKGlkX2NvbHMgPSBjdXQsIG5hbWVzX2Zyb20gPSBjbGFyaXR5LCB2YWx1ZXNfZnJvbSA9IGNvdW50KSAlPiUgDQogIA0KICAjIE5vdyBhZGQgdGhlIHJvdyBhbmQgY29sdW1uIHRvdGFscyB1c2luZyB0aGUgYGphbml0b3JgIHBhY2thZ2UNCiAgamFuaXRvcjo6YWRvcm5fdG90YWxzKHdoZXJlID0gYygicm93IiwgImNvbCIpKSAlPiUNCiAgDQogICMgUmVjb3ZlcnQgdG8gdGliYmxlIHNpbmNlIGphbml0b3IgZ2l2ZXMgYSAidGFieWwiIGZvcm1hdCAoIHdoaWNoIGNhbiBiZSB1c2VmdWwgKQ0KICBhc190aWJibGUoKQ0KDQpgYGANCg0KDQoNCk5vdyB0aGF0IHdlIGhhdmUgQ29udGluZ2VuY3kgVGFibGVzLCB3ZSBjYW4gcGxvdCB0aGVzZToNCg0KIyBQbG90dGluZyBDYXRlZ29yaWNhbCBEYXRhDQoNCiMjIFRoZSBgdGl0YW5pY2AgZGF0YXNldA0KDQpgYGB7cn0NCmRhdGEoInRpdGFuaWMiKQ0KdGl0YW5pYw0KDQpgYGANCg0KDQojIyMgYHRpdGFuaWNgIEJhciBQbG90cw0KDQpgYGB7ciB0aXRhbmljLWJhci1wbG90fQ0KIyBVc2UgZHBseXIgYW5kIGdncGxvdA0KDQoNCg0KDQpgYGANCg0KDQoNCg0KIyMjIGB0aXRhbmljYCBNb3NhaWMgUGxvdA0KDQpgYGB7ciB0aXRhbmljLW1vc2FpYy1wbG90fQ0KIyBUcnkgdGhlIG1vc2FpYyBwYWNrYWdlIGFuZCB0aGUgZ2dtb3NhaWMgcGFja2FnZQ0KDQoNCg0KDQpgYGANCg0KIyMjIGB0aXRhbmljYCBCYWxsb29uIFBsb3QNCg0KYGBge3IgdGl0YW5pYy1iYWxsb29uLXBsb3R9DQojIHVzZSBnZ3B1YnINCg0KDQoNCg0KYGBgDQoNCg0KIyMgVGhlIGBoaXBwb2NvcnB1c2AgZGF0YXNldCBmcm9tIEthZ2dsZQ0KDQpgYGB7ciwgZWNobz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShkb3dubG9hZHRoaXMpDQpoaXBwbyA8LSByZWFkLmNzdigiZGF0YS9oaXBwb0NvcnB1c1YyLmNzdiIpDQpkb3dubG9hZF90aGlzKGhpcHBvLA0KICAgICNwYXRoID0gImRhdGEvaGlwcG9Db3JwdXNWMi5jc3YiLA0KICAgIG91dHB1dF9uYW1lID0gImhpcHBvY29ycHVzIiwNCiAgICBvdXRwdXRfZXh0ZW5zaW9uID0gIi5jc3YiLA0KICAgIGJ1dHRvbl9sYWJlbCA9ICJEb3dubG9hZCBkYXRhIGFzIGNzdiIsDQogICAgYnV0dG9uX3R5cGUgPSAiaW5mbyIsDQogICAgaGFzX2ljb24gPSBUUlVFLA0KICAgIGljb24gPSAiZmEgZmEtc2F2ZSINCiAgKQ0KDQpgYGANCg0KVGhpcyBpcyBhIGRhdGFzZXQgZnJvbQ0KW0thZ2dsZV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYXRhc2V0cy9zYXVyYWJoc2hhaGFuZS9oaXBwb2NvcnB1cz9zZWxlY3Q9aGlwcG9Db3JwdXNWMi5jc3YpDQphbmQgaXMgYmFzZWQgb24gUmVmZXJlbmNlIDIuDQoNCj4gSGlwcG9jb3JwdXMgaXMgZGF0YXNldCBvZiA2ODU0IEVuZ2xpc2ggZGlhcnkgbGlrZSBzaG9ydCBzdG9yaWVzIGFib3V0DQo+IHJlY2FsbGVkIGFuZCBpbWFnaW5lZCBldmVudHMuIFVzaW5nIGEgY3Jvd2Rzb3VyY2luZyBmcmFtZXdvcmsgdGhlDQo+IHJlc3BlY3RpdmUgb3duZXJzIG9mIHRoaXMgZGF0YXNldHMgY29sbGVjdGVkIHJlY2FsbGVkIHN0b3JpZXMgYW5kDQo+IHN1bW1hcmllcyBmcm9tIHdvcmtlcnMsIHRoZW4gcHJvdmlkZWQgdGhlc2UgY29sbGVjdGVkIHN1bW1hcmllcyB0bw0KPiBvdGhlciB3b3JrZXJzIHdobyB3cml0ZSBpbWFnaW5lZCBzdG9yaWVzLiBNb250aHMgbGF0ZXIgZGF0YXNldA0KPiBjcmVhdG9ycyBjb2xsZWN0ZWQgYSByZXRvbGQgdmVyc2lvbiBvZiB0aGUgcmVjYWxsZWQgc3RvcmllcyBmcm9tIHRoZQ0KPiBzdWJzZXQgb2YgcmVjYWxsZWQgYXV0aG9ycy4gRGF0YXNldCBjb250YWlucyBhdXRob3IgZGVtb2dyYXBoaWNzIChhZ2UsDQo+IGdlbmRlciwgcmFjZSksIHRoZWlyIG9wZW5uZXNzIHRvIGV4cGVyaWVuY2UsIGFzIHdlbGwgYXMgc29tZSB2YXJpYWJsZXMNCj4gcmVnYXJkaW5nIHRoZSBhdXRob3IncyByZWxhdGlvbnNoaXAgdG8gdGhlIGV2ZW50IChlLmcuLCBob3cgcGVyc29uYWwNCj4gdGhlIGV2ZW50IGlzLCBob3cgb2Z0ZW4gdGhleSB0ZWxsIGl0cyBzdG9yeSwgZXRjLikNCg0KQXBhcnQgZnJvbSBtZXRhZGF0YSBwZXJ0YWluaW5nIHRvIGVhY2ggcmVzcG9uZGVudCwgdGhlcmUgYXJlIDQgKkxpa2VydA0KU2NhbGUqIHZhcmlhYmxlczoNCg0KLSAgIGBkaXN0cmFjdGVkYDogSG93IGRpc3RyYWN0ZWQgd2VyZSB5b3Ugd2hpbGUgd3JpdGluZyB5b3VyIHN0b3J5Pw0KICAgICg1LXBvaW50IExpa2VydCkNCi0gICBgZHJhaW5pbmdgOiBIb3cgdGF4aW5nL2RyYWluaW5nIHdhcyB3cml0aW5nIGZvciB5b3UgZW1vdGlvbmFsbHk/DQogICAgKDUtcG9pbnQgTGlrZXJ0KQ0KLSAgIGBmcmVxdWVuY3lgOiBIb3cgb2Z0ZW4gZG8geW91IHRoaW5rIGFib3V0IG9yIHRhbGsgYWJvdXQgdGhpcyBldmVudD8NCiAgICAoNS1wb2ludCBMaWtlcnQpDQotICAgYGltcG9ydGFuY2VgOiBIb3cgaW1wYWN0ZnVsLCBpbXBvcnRhbnQsIG9yIHBlcnNvbmFsIGlzIHRoaXMNCiAgICBzdG9yeS90aGlzIGV2ZW50IHRvIHlvdT8gKDUtcG9pbnQgTGlrZXJ0KS4gUGxvdCB0aGVzZSB1c2luZyB0aGUNCiAgICBwYWNrYWdlIGBzalBsb3RgLiBDYW4geW91IGFsc28gdHJ5IGEgYGdncGxvdGA/DQoNCmBgYHtyIGhpcHBvY29ycHVzLWxpa2VydH0NCg0KDQpgYGANCg0KDQojIyBBIGRhdGFzZXQgZnJvbSB0aGUgYHZjZEV4dHJhYCBwYWNrYWdlDQoNClBpY2sgb25lIG9mIHRoZSBmYWlybHkgbGFyZ2UgQ2F0ZWdvcmljYWwgZGF0YXNldHMgdGhhdCBhcmUgYnVpbHQgaW50byBgdmNkRXh0cmFgOiB0eXBlIGBkYXRhKHBhY2thZ2UgPSAidmNkRXh0cmEiKWAgaW4geW91ciBDb25zb2xlLg0KDQpDcmVhdGU6ICANCi0gQ29udGluZ2VuY3kgVGFibGUNCi0gQSBCYXIgUGxvdA0KLSBBIE1vc2FpYyBQbG90DQotIEEgQmFsbG9vbiBQbG90DQoNCg0KIyBDb25jbHVzaW9uDQoNCldyaXRlIGEgZmV3IGNvbW1lbnRzIG9uIHRoZSBkYXRhIGFuZCB2aXN1YWxpemF0aW9ucy4gRGlkIHRoZXkgY29udmV5IGEgc3Rvcnkgb2Ygc29ydHM/DQoNCg0KIyBSZWZlcmVuY2VzDQoNCjEuICBBIGRldGFpbGVkIGFuYWx5c2lzIG9mIHRoZSBOSEFORVMgZGF0YXNldCwNCiAgICA8aHR0cHM6Ly9hd2FnYW1hbi5wZW9wbGUuYW1oZXJzdC5lZHUvc3RhdDIzMC9TdGF0MjMwQ29kZUNvbXBpbGF0aW9uRXhhbXBsZUNvZGVVc2luZ05IQU5FUy5wZGY+DQoNCjIuICBNYWFydGVuIFNhcCwgRXJpYyBIb3J2aXR6LCBZZWppbiBDaG9pLCBOb2FoIEEuIFNtaXRoLCBhbmQgSmFtZXMNCiAgICBQZW5uZWJha2VyICgyMDIwKSAqUmVjb2xsZWN0aW9uIHZlcnN1cyBJbWFnaW5hdGlvbjogRXhwbG9yaW5nIEh1bWFuDQogICAgTWVtb3J5IGFuZCBDb2duaXRpb24gdmlhIE5ldXJhbCBMYW5ndWFnZSBNb2RlbHMuKiBBQ0wuDQo=