# Load libraries
library(tidyr)
library(dplyr)
library(ggplot2)

The comics Dataset

Two publishers, Marvel and DC, have created a host of superheroes that have made their way into popular culture. You’re probably familiar with Batman and Spiderman, but what about Mor the Mighty? The comics dataset has information on all comic characters that have been introduced by DC and Marvel. If we type the name of the dataset at the console, we get the first few rows and columns. Here we see that each row, or case, is a different character and each column, or variable, is a different observation made on that character. At the top it tell us the dimensions of this dataset: over 23,000 cases and 11 variables. Right under the variable names, it tells us that all three of these are factors, R’s preferred way to represent categorical variables.

# Load comics dataset
comics <- read.csv("C:/Users/JuanFer Mosquera/Documents/datasets/comics.csv")

Working with factors

It’s clear that the alignment variable can be “good” or “neutral”, but what other values are possible? If we run levels on the align column, we learn that there are in fact four possible alignments, including reformed criminal.

comics <- comics %>%
  mutate(across(c(id, align, eye, gender, hair, publisher), as.factor))
# Working with factors 
class(comics$align)
[1] "factor"
levels(comics$align)
[1] "Bad"                "Good"               "Neutral"            "Reformed Criminals"
levels(comics$id)
[1] "No Dual" "Public"  "Secret"  "Unknown"
levels(comics$gender)
[1] "Female" "Male"   "Other" 
levels(comics$publisher)
[1] "dc"     "marvel"

A common way to represent the number of cases that fall into each combination of levels of two categorical variables, like these, is with a contingency table. This is done with the table() command, which takes as arguments the variables that you’re interested in.

tab <- table(comics$id, comics$align)
tab_a <- table(comics$gender, comics$align)

Dropping levels

The contingency table revealed that there are some levels that have very low counts. To simplify the analysis, it often helps to drop such levels.

In R, this requires two steps: first filtering out any rows with the levels that have very low counts, then removing these levels from the factor variable with droplevels().

This is because the droplevels() function would keep levels that have just 1 or 2 counts; it only drops levels that don’t exist in a dataset.

tab
         
           Bad Good Neutral Reformed Criminals
  No Dual  474  647     390                  0
  Public  2172 2930     965                  1
  Secret  4493 2475     959                  1
  Unknown    7    0       2                  0
tab_a
        
          Bad Good Neutral Reformed Criminals
  Female 1573 2490     836                  1
  Male   7561 4809    1799                  2
  Other    32   17      17                  0

Let´s see which characters are not reformed criminals:

# Remove align level
comics_filtered <- comics %>%
  filter(align != 'Reformed Criminals') %>%
  droplevels()

comics_filtered

Bar chart

Let´s construct two side-by-side bar charts of the comics data. This shows that there can often be two or more options for presenting the same data. Passing the argument position = "dodge" to geom_bar() says that you want a side-by-side (i.e. not stacked) bar chart.

# Create side-by-side bar chart of gender by alignment
ggplot(comics, aes(x = align, fill = gender)) + geom_bar(position = "dodge")

# Create side-by-side bar chart of alignment by gender
ggplot(comics, aes(x = gender, fill = align)) + geom_bar(position = "dodge") +
  theme(axis.text.x = element_text(angle = 90))

# Create a fill barchart of id by align
ggplot(comics, aes(x = id, fill = align)) + geom_bar()

Let’s look carefully at how this is constructed: each colored bar segment actually corresponds to a count in our table, with the x-axis and the fill= color indicating the category that we’re looking at. Several things pop out, like the fact that there are very few characters whose identities are unknown, but there are many where we don’t have data; that’s what the NAs mean.

The single largest bar segment corresponds to the most common category: characters with secret identities that are also bad. We can look across the identity types, though, and realize that bad is not always the largest category. This indicates that there is indeed an association between alignment and identity.

Counts vs. proportions

Sometimes raw counts of cases can be useful, but often it’s the proportions that are more interesting. We can do our best to compute these proportions in our head or we could do it explicitly.

From counts to proportions

Let’s return to our table of counts of cases by identity and alignment. If we wanted to instead get a sense of the proportion of all cases that fell into each category, we can take the original table of counts, saved as tab_cnt, and provide it as input to the prop.table() function. We see here that the single largest category are characters that are bad and secret at about \(29%\) of characters. Also note that because these are all proportions out of the whole dataset, the sum of all of these proportions is 1.

# Simlify display format
options(scipen = 999, digits = 3)
tab_cnt <- table(comics$id, comics$align)
tab_cnt
         
           Bad Good Neutral Reformed Criminals
  No Dual  474  647     390                  0
  Public  2172 2930     965                  1
  Secret  4493 2475     959                  1
  Unknown    7    0       2                  0
prop.table(tab_cnt)
         
                Bad      Good   Neutral Reformed Criminals
  No Dual 0.0305491 0.0416989 0.0251353          0.0000000
  Public  0.1399845 0.1888373 0.0621939          0.0000644
  Secret  0.2895721 0.1595128 0.0618072          0.0000644
  Unknown 0.0004511 0.0000000 0.0001289          0.0000000

Conditional proportions

If we’re curious about systematic associations between variables, we should look to conditional proportions. An example of a conditional proportion is the proportion of public identity characters that are good.

To build a table of these conditional proportions, add a 1 as the second argument, specifying that you’d like to condition on the rows. We see here that around \(57%\) of all secret characters are bad. Because we’re conditioning on identity, it’s every row that now sums to one. To condition on the columns instead, change that argument to 2. Now it’s the columns that sum to one and we learn, for example, that the proportion of bad characters that are secret is around 63%. As the number of cells in these tables gets large, it becomes much easier to make sense of your data using graphics. The bar chart is still a good choice, but we’re going to need to add some options.

prop.table(tab_cnt, 1)
         
               Bad     Good  Neutral Reformed Criminals
  No Dual 0.313700 0.428193 0.258107           0.000000
  Public  0.357943 0.482861 0.159031           0.000165
  Secret  0.566726 0.312185 0.120964           0.000126
  Unknown 0.777778 0.000000 0.222222           0.000000
prop.table(tab_cnt, 2)
         
               Bad     Good  Neutral Reformed Criminals
  No Dual 0.066331 0.106907 0.168394           0.000000
  Public  0.303946 0.484137 0.416667           0.500000
  Secret  0.628743 0.408956 0.414076           0.500000
  Unknown 0.000980 0.000000 0.000864           0.000000

Conditional bar chart

Here is the code for the bar chart based on counts. We want to condition on whatever is on the x axis and stretch those bars to each add up to a total proportion of 1, so we add the position equals fill option to the geom bar function.

ggplot(comics, aes(id, fill = align)) + geom_bar(position = "fill") + 
  ylab("Proportion")

ggplot(comics, aes(align, fill = id)) + geom_bar(position = "fill") +
  ylab("Proportion")

tab <- table(comics$align, comics$gender)
options(scipen = 999, digits = 3)
prop.table(tab)
                    
                        Female      Male     Other
  Bad                0.0821968 0.3950985 0.0016722
  Good               0.1301144 0.2512933 0.0008883
  Neutral            0.0436850 0.0940064 0.0008883
  Reformed Criminals 0.0000523 0.0001045 0.0000000
prop.table(tab, 2)
                    
                       Female     Male    Other
  Bad                0.321020 0.533554 0.484848
  Good               0.508163 0.339355 0.257576
  Neutral            0.170612 0.126949 0.257576
  Reformed Criminals 0.000204 0.000141 0.000000

Plot of gender by align

ggplot(comics_filtered, aes(align, fill = gender)) + 
  geom_bar()

Plot proportion of gender, conditional on align

ggplot(comics_filtered, aes(align, fill = gender)) + geom_bar(position = "fill") +
  ylab("proportion")

Distribution of one variable

Marginal distribution

To compute a table of counts for a single variable like id, just provide vector into into the table function by the sole argument. One way to think of what we’ve done is to take the original two-way table and then, sum the cells across each level of align. Since we’ve summed over the margins of the other variables, this is sometimes known as a marginal distribution.

table(comics$id)

No Dual  Public  Secret Unknown 
   1788    6994    8698       9 
tab_cnt <- table(comics$id, comics$align)
tab_cnt
         
           Bad Good Neutral Reformed Criminals
  No Dual  474  647     390                  0
  Public  2172 2930     965                  1
  Secret  4493 2475     959                  1
  Unknown    7    0       2                  0

Simple bar chart

The syntax to create the simple bar chart is straightforward as well, just remove the fill equals align argument.

ggplot(comics, aes(id)) + 
  geom_bar()

Faceting

Another useful way to form the distribution of a single variable is to condition on a particular value of another variable. We might be interested, for example, in the distribution of id for all neutral characters. We could either filter the dataset and build a bar chart using only cases where alignment was neutral, or we could use a technique called faceting. Faceting breaks the data into subsets based on the levels of a categorical variable and then constructs a plot for each.

Faceted bar charts

To implement this in ggplot2, we just need to add a faceting layer: the facet_wrap() function, then a tilde (~), which can be read as “broken down by” and then our variable align. The result is three simple bar charts side-by-side, the first one corresponding to the distribution of id within all cases that have a bad alignment, and so on, for good and neutral alignments. If this plot feels familiar, it should.

ggplot(comics_filtered, aes(id)) + geom_bar() + 
  facet_wrap(~ align)

Marginal bar chart

If you are interested in the distribution of alignment of all superheroes, it makes sense to construct a bar chart for just that single variable.

You can improve the interpretability of the plot, though, by implementing some sensible ordering. Superheroes that are "Neutral" show an alignment between "Good" and "Bad", so it makes sense to put that bar in the middle.

comics$align <- factor(comics$align, levels = c("Bad", "Neutral", "Good"))

# Create the plot of align
ggplot(comics, aes(align)) + geom_bar()

Conditional bar chart

Now, if you want to break down the distribution of alignment based on gender, you’re looking for conditional distributions.

You could make these by creating multiple filtered datasets (one for each gender) or by faceting the plot of alignment based on gender. As a point of comparison, we’ve provided your plot of the marginal distribution of alignment from the last exercise.

ggplot(comics_filtered, aes(align)) + geom_bar() + 
  facet_wrap(~ gender)

Improve pie chart

The pie chart is a very common way to represent the distribution of a single categorical variable, but they can be more difficult to interpret than bar charts.

ggplot(comics, aes(align)) +
  geom_bar(fill = "chartreuse") +
  theme(axis.text.x = element_text(angle = 90))

END

LS0tDQp0aXRsZTogIkV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMiDQphdXRob3I6IEp1YW4gRmVybmFuZG8gTW9zcXVlcmENCmRhdGU6IDIwMjQtMDMtMDINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCmBgYHtyfQ0KIyBMb2FkIGxpYnJhcmllcw0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpgYGANCg0KIyMgKipUaGUgY29taWNzIERhdGFzZXQqKg0KDQpUd28gcHVibGlzaGVycywgTWFydmVsIGFuZCBEQywgaGF2ZSBjcmVhdGVkIGEgaG9zdCBvZiBzdXBlcmhlcm9lcyB0aGF0IGhhdmUgbWFkZSB0aGVpciB3YXkgaW50byBwb3B1bGFyIGN1bHR1cmUuIFlvdSdyZSBwcm9iYWJseSBmYW1pbGlhciB3aXRoIEJhdG1hbiBhbmQgU3BpZGVybWFuLCBidXQgd2hhdCBhYm91dCBNb3IgdGhlIE1pZ2h0eT8gVGhlIGNvbWljcyBkYXRhc2V0IGhhcyBpbmZvcm1hdGlvbiBvbiBhbGwgY29taWMgY2hhcmFjdGVycyB0aGF0IGhhdmUgYmVlbiBpbnRyb2R1Y2VkIGJ5IERDIGFuZCBNYXJ2ZWwuIElmIHdlIHR5cGUgdGhlIG5hbWUgb2YgdGhlIGRhdGFzZXQgYXQgdGhlIGNvbnNvbGUsIHdlIGdldCB0aGUgZmlyc3QgZmV3IHJvd3MgYW5kIGNvbHVtbnMuIEhlcmUgd2Ugc2VlIHRoYXQgZWFjaCByb3csIG9yIGNhc2UsIGlzIGEgZGlmZmVyZW50IGNoYXJhY3RlciBhbmQgZWFjaCBjb2x1bW4sIG9yIHZhcmlhYmxlLCBpcyBhIGRpZmZlcmVudCBvYnNlcnZhdGlvbiBtYWRlIG9uIHRoYXQgY2hhcmFjdGVyLiBBdCB0aGUgdG9wIGl0IHRlbGwgdXMgdGhlIGRpbWVuc2lvbnMgb2YgdGhpcyBkYXRhc2V0OiBvdmVyIDIzLDAwMCBjYXNlcyBhbmQgMTEgdmFyaWFibGVzLiBSaWdodCB1bmRlciB0aGUgdmFyaWFibGUgbmFtZXMsIGl0IHRlbGxzIHVzIHRoYXQgYWxsIHRocmVlIG9mIHRoZXNlIGFyZSBmYWN0b3JzLCBSJ3MgcHJlZmVycmVkIHdheSB0byByZXByZXNlbnQgY2F0ZWdvcmljYWwgdmFyaWFibGVzLg0KDQpgYGB7cn0NCiMgTG9hZCBjb21pY3MgZGF0YXNldA0KY29taWNzIDwtIHJlYWQuY3N2KCJDOi9Vc2Vycy9KdWFuRmVyIE1vc3F1ZXJhL0RvY3VtZW50cy9kYXRhc2V0cy9jb21pY3MuY3N2IikNCmBgYA0KDQpgYGB7cn0NCmhlYWQoY29taWNzLCAyMCkNCmBgYA0KDQojIyMgKipXb3JraW5nIHdpdGggZmFjdG9ycyoqDQoNCkl0J3MgY2xlYXIgdGhhdCB0aGUgYWxpZ25tZW50IHZhcmlhYmxlIGNhbiBiZSAiKipnb29kKioiIG9yICIqKm5ldXRyYWwqKiIsIGJ1dCB3aGF0IG90aGVyIHZhbHVlcyBhcmUgcG9zc2libGU/IElmIHdlIHJ1biBsZXZlbHMgb24gdGhlIGFsaWduIGNvbHVtbiwgd2UgbGVhcm4gdGhhdCB0aGVyZSBhcmUgaW4gZmFjdCBmb3VyIHBvc3NpYmxlIGFsaWdubWVudHMsIGluY2x1ZGluZyAqKnJlZm9ybWVkIGNyaW1pbmFsKiouDQoNCmBgYHtyfQ0KY29taWNzIDwtIGNvbWljcyAlPiUNCiAgbXV0YXRlKGFjcm9zcyhjKGlkLCBhbGlnbiwgZXllLCBnZW5kZXIsIGhhaXIsIHB1Ymxpc2hlciksIGFzLmZhY3RvcikpDQpgYGANCg0KYGBge3J9DQojIFdvcmtpbmcgd2l0aCBmYWN0b3JzIA0KY2xhc3MoY29taWNzJGFsaWduKQ0KbGV2ZWxzKGNvbWljcyRhbGlnbikNCmxldmVscyhjb21pY3MkaWQpDQpsZXZlbHMoY29taWNzJGdlbmRlcikNCmxldmVscyhjb21pY3MkcHVibGlzaGVyKQ0KYGBgDQoNCkEgY29tbW9uIHdheSB0byByZXByZXNlbnQgdGhlIG51bWJlciBvZiBjYXNlcyB0aGF0IGZhbGwgaW50byBlYWNoIGNvbWJpbmF0aW9uIG9mIGxldmVscyBvZiB0d28gY2F0ZWdvcmljYWwgdmFyaWFibGVzLCBsaWtlIHRoZXNlLCBpcyB3aXRoIGEgKipjb250aW5nZW5jeSB0YWJsZSoqLiBUaGlzIGlzIGRvbmUgd2l0aCB0aGUgYHRhYmxlKClgIGNvbW1hbmQsIHdoaWNoIHRha2VzIGFzIGFyZ3VtZW50cyB0aGUgdmFyaWFibGVzIHRoYXQgeW91J3JlIGludGVyZXN0ZWQgaW4uDQoNCmBgYHtyfQ0KdGFiIDwtIHRhYmxlKGNvbWljcyRpZCwgY29taWNzJGFsaWduKQ0KdGFiX2EgPC0gdGFibGUoY29taWNzJGdlbmRlciwgY29taWNzJGFsaWduKQ0KYGBgDQoNCiMjIyAqKkRyb3BwaW5nIGxldmVscyoqDQoNClRoZSBjb250aW5nZW5jeSB0YWJsZSByZXZlYWxlZCB0aGF0IHRoZXJlIGFyZSBzb21lIGxldmVscyB0aGF0IGhhdmUgdmVyeSBsb3cgY291bnRzLiBUbyBzaW1wbGlmeSB0aGUgYW5hbHlzaXMsIGl0IG9mdGVuIGhlbHBzIHRvIGRyb3Agc3VjaCBsZXZlbHMuDQoNCkluIGBSYCwgdGhpcyByZXF1aXJlcyB0d28gc3RlcHM6IGZpcnN0ICoqZmlsdGVyaW5nIG91dCBhbnkgcm93cyB3aXRoIHRoZSBsZXZlbHMgdGhhdCBoYXZlIHZlcnkgbG93IGNvdW50cyoqLCB0aGVuICoqcmVtb3ZpbmcgdGhlc2UgbGV2ZWxzIGZyb20gdGhlIGZhY3RvciB2YXJpYWJsZSoqIHdpdGggYGRyb3BsZXZlbHMoKWAuDQoNClRoaXMgaXMgYmVjYXVzZSB0aGUgYGRyb3BsZXZlbHMoKWAgZnVuY3Rpb24gd291bGQga2VlcCBsZXZlbHMgdGhhdCBoYXZlIGp1c3QgMSBvciAyIGNvdW50czsgaXQgb25seSBkcm9wcyBsZXZlbHMgdGhhdCBkb24ndCBleGlzdCBpbiBhIGRhdGFzZXQuDQoNCmBgYHtyfQ0KdGFiDQp0YWJfYQ0KYGBgDQoNCkxldMK0cyBzZWUgd2hpY2ggY2hhcmFjdGVycyBhcmUgbm90IHJlZm9ybWVkIGNyaW1pbmFsczoNCg0KYGBge3J9DQojIFJlbW92ZSBhbGlnbiBsZXZlbA0KY29taWNzX2ZpbHRlcmVkIDwtIGNvbWljcyAlPiUNCiAgZmlsdGVyKGFsaWduICE9ICdSZWZvcm1lZCBDcmltaW5hbHMnKSAlPiUNCiAgZHJvcGxldmVscygpDQoNCmNvbWljc19maWx0ZXJlZA0KYGBgDQoNCiMjIyAqKkJhciBjaGFydCoqDQoNCkxldMK0cyBjb25zdHJ1Y3QgdHdvIHNpZGUtYnktc2lkZSBiYXIgY2hhcnRzIG9mIHRoZcKgYGNvbWljc2DCoGRhdGEuIFRoaXMgc2hvd3MgdGhhdCB0aGVyZSBjYW4gb2Z0ZW4gYmUgdHdvIG9yIG1vcmUgb3B0aW9ucyBmb3IgcHJlc2VudGluZyB0aGUgc2FtZSBkYXRhLiBQYXNzaW5nIHRoZSBhcmd1bWVudMKgYHBvc2l0aW9uID0gImRvZGdlImDCoHRvwqBgZ2VvbV9iYXIoKWDCoHNheXMgdGhhdCB5b3Ugd2FudCBhIHNpZGUtYnktc2lkZSAoaS5lLiBub3Qgc3RhY2tlZCkgYmFyIGNoYXJ0Lg0KDQpgYGB7cn0NCiMgQ3JlYXRlIHNpZGUtYnktc2lkZSBiYXIgY2hhcnQgb2YgZ2VuZGVyIGJ5IGFsaWdubWVudA0KZ2dwbG90KGNvbWljcywgYWVzKHggPSBhbGlnbiwgZmlsbCA9IGdlbmRlcikpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKQ0KYGBgDQoNCmBgYHtyfQ0KIyBDcmVhdGUgc2lkZS1ieS1zaWRlIGJhciBjaGFydCBvZiBhbGlnbm1lbnQgYnkgZ2VuZGVyDQpnZ3Bsb3QoY29taWNzLCBhZXMoeCA9IGdlbmRlciwgZmlsbCA9IGFsaWduKSkgKyBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpDQpgYGANCg0KYGBge3J9DQojIENyZWF0ZSBhIGZpbGwgYmFyY2hhcnQgb2YgaWQgYnkgYWxpZ24NCmdncGxvdChjb21pY3MsIGFlcyh4ID0gaWQsIGZpbGwgPSBhbGlnbikpICsgZ2VvbV9iYXIoKQ0KYGBgDQoNCkxldCdzIGxvb2sgY2FyZWZ1bGx5IGF0IGhvdyB0aGlzIGlzIGNvbnN0cnVjdGVkOiBlYWNoIGNvbG9yZWQgYmFyIHNlZ21lbnQgYWN0dWFsbHkgY29ycmVzcG9uZHMgdG8gYSBjb3VudCBpbiBvdXIgdGFibGUsIHdpdGggdGhlIHgtYXhpcyBhbmQgdGhlIGBmaWxsPWAgY29sb3IgaW5kaWNhdGluZyB0aGUgY2F0ZWdvcnkgdGhhdCB3ZSdyZSBsb29raW5nIGF0LiBTZXZlcmFsIHRoaW5ncyBwb3Agb3V0LCBsaWtlIHRoZSBmYWN0IHRoYXQgdGhlcmUgYXJlIHZlcnkgZmV3IGNoYXJhY3RlcnMgd2hvc2UgKippZGVudGl0aWVzIGFyZSB1bmtub3duKiosIGJ1dCB0aGVyZSBhcmUgbWFueSB3aGVyZSB3ZSBkb24ndCBoYXZlIGRhdGE7IHRoYXQncyB3aGF0IHRoZSBOQXMgbWVhbi4NCg0KVGhlIHNpbmdsZSBsYXJnZXN0IGJhciBzZWdtZW50IGNvcnJlc3BvbmRzIHRvIHRoZSBtb3N0IGNvbW1vbiBjYXRlZ29yeTogY2hhcmFjdGVycyB3aXRoIHNlY3JldCBpZGVudGl0aWVzIHRoYXQgYXJlIGFsc28gYmFkLiBXZSBjYW4gbG9vayBhY3Jvc3MgdGhlIGlkZW50aXR5IHR5cGVzLCB0aG91Z2gsIGFuZCByZWFsaXplIHRoYXQgYmFkIGlzIG5vdCBhbHdheXMgdGhlIGxhcmdlc3QgY2F0ZWdvcnkuIFRoaXMgaW5kaWNhdGVzIHRoYXQgdGhlcmUgaXMgaW5kZWVkIGFuIGFzc29jaWF0aW9uIGJldHdlZW4gYWxpZ25tZW50IGFuZCBpZGVudGl0eS4NCg0KIyMgKipDb3VudHMgdnMuIHByb3BvcnRpb25zKioNCg0KU29tZXRpbWVzIHJhdyBjb3VudHMgb2YgY2FzZXMgY2FuIGJlIHVzZWZ1bCwgYnV0IG9mdGVuIGl0J3MgdGhlIHByb3BvcnRpb25zIHRoYXQgYXJlIG1vcmUgaW50ZXJlc3RpbmcuIFdlIGNhbiBkbyBvdXIgYmVzdCB0byBjb21wdXRlIHRoZXNlIHByb3BvcnRpb25zIGluIG91ciBoZWFkIG9yIHdlIGNvdWxkIGRvIGl0IGV4cGxpY2l0bHkuDQoNCiMjIyBGcm9tIGNvdW50cyB0byBwcm9wb3J0aW9ucw0KDQpMZXQncyByZXR1cm4gdG8gb3VyIHRhYmxlIG9mIGNvdW50cyBvZiBjYXNlcyBieSBpZGVudGl0eSBhbmQgYWxpZ25tZW50LiBJZiB3ZSB3YW50ZWQgdG8gaW5zdGVhZCBnZXQgYSBzZW5zZSBvZiB0aGUgcHJvcG9ydGlvbiBvZiBhbGwgY2FzZXMgdGhhdCBmZWxsIGludG8gZWFjaCBjYXRlZ29yeSwgd2UgY2FuIHRha2UgdGhlIG9yaWdpbmFsIHRhYmxlIG9mIGNvdW50cywgc2F2ZWQgYXMgYHRhYl9jbnRgLCBhbmQgcHJvdmlkZSBpdCBhcyBpbnB1dCB0byB0aGUgYHByb3AudGFibGUoKWAgZnVuY3Rpb24uIFdlIHNlZSBoZXJlIHRoYXQgdGhlIHNpbmdsZSBsYXJnZXN0IGNhdGVnb3J5IGFyZSBjaGFyYWN0ZXJzIHRoYXQgYXJlIGJhZCBhbmQgc2VjcmV0IGF0IGFib3V0ICQyOSUkIG9mIGNoYXJhY3RlcnMuIEFsc28gbm90ZSB0aGF0IGJlY2F1c2UgdGhlc2UgYXJlIGFsbCBwcm9wb3J0aW9ucyBvdXQgb2YgdGhlIHdob2xlIGRhdGFzZXQsIHRoZSBzdW0gb2YgYWxsIG9mIHRoZXNlIHByb3BvcnRpb25zIGlzIDEuDQoNCmBgYHtyfQ0KIyBTaW1saWZ5IGRpc3BsYXkgZm9ybWF0DQpvcHRpb25zKHNjaXBlbiA9IDk5OSwgZGlnaXRzID0gMykNCnRhYl9jbnQgPC0gdGFibGUoY29taWNzJGlkLCBjb21pY3MkYWxpZ24pDQp0YWJfY250DQpgYGANCg0KYGBge3J9DQpwcm9wLnRhYmxlKHRhYl9jbnQpDQpgYGANCg0KIyMjICoqQ29uZGl0aW9uYWwgcHJvcG9ydGlvbnMqKg0KDQpJZiB3ZSdyZSBjdXJpb3VzIGFib3V0IHN5c3RlbWF0aWMgYXNzb2NpYXRpb25zIGJldHdlZW4gdmFyaWFibGVzLCB3ZSBzaG91bGQgbG9vayB0byBjb25kaXRpb25hbCBwcm9wb3J0aW9ucy4gQW4gZXhhbXBsZSBvZiBhIGNvbmRpdGlvbmFsIHByb3BvcnRpb24gKippcyB0aGUgcHJvcG9ydGlvbiBvZiBwdWJsaWMgaWRlbnRpdHkgY2hhcmFjdGVycyB0aGF0IGFyZSBnb29kKiouDQoNClRvIGJ1aWxkIGEgdGFibGUgb2YgdGhlc2UgY29uZGl0aW9uYWwgcHJvcG9ydGlvbnMsIGFkZCBhIDEgYXMgdGhlIHNlY29uZCBhcmd1bWVudCwgc3BlY2lmeWluZyB0aGF0IHlvdSdkIGxpa2UgdG8gY29uZGl0aW9uIG9uIHRoZSByb3dzLiBXZSBzZWUgaGVyZSB0aGF0IGFyb3VuZCAkNTclJCBvZiBhbGwgc2VjcmV0IGNoYXJhY3RlcnMgYXJlIGJhZC4gQmVjYXVzZSB3ZSdyZSBjb25kaXRpb25pbmcgb24gaWRlbnRpdHksIGl0J3MgZXZlcnkgcm93IHRoYXQgbm93IHN1bXMgdG8gb25lLiBUbyBjb25kaXRpb24gb24gdGhlIGNvbHVtbnMgaW5zdGVhZCwgY2hhbmdlIHRoYXQgYXJndW1lbnQgdG8gMi4gTm93IGl0J3MgdGhlIGNvbHVtbnMgdGhhdCBzdW0gdG8gb25lIGFuZCB3ZSBsZWFybiwgZm9yIGV4YW1wbGUsIHRoYXQgdGhlIHByb3BvcnRpb24gb2YgYmFkIGNoYXJhY3RlcnMgdGhhdCBhcmUgc2VjcmV0IGlzIGFyb3VuZCA2MyUuIEFzIHRoZSBudW1iZXIgb2YgY2VsbHMgaW4gdGhlc2UgdGFibGVzIGdldHMgbGFyZ2UsIGl0IGJlY29tZXMgbXVjaCBlYXNpZXIgdG8gbWFrZSBzZW5zZSBvZiB5b3VyIGRhdGEgdXNpbmcgZ3JhcGhpY3MuIFRoZSBiYXIgY2hhcnQgaXMgc3RpbGwgYSBnb29kIGNob2ljZSwgYnV0IHdlJ3JlIGdvaW5nIHRvIG5lZWQgdG8gYWRkIHNvbWUgb3B0aW9ucy4NCg0KYGBge3J9DQpwcm9wLnRhYmxlKHRhYl9jbnQsIDEpDQpgYGANCg0KYGBge3J9DQpwcm9wLnRhYmxlKHRhYl9jbnQsIDIpDQpgYGANCg0KIyMjICoqQ29uZGl0aW9uYWwgYmFyIGNoYXJ0KioNCg0KSGVyZSBpcyB0aGUgY29kZSBmb3IgdGhlIGJhciBjaGFydCBiYXNlZCBvbiBjb3VudHMuIFdlIHdhbnQgdG8gY29uZGl0aW9uIG9uIHdoYXRldmVyIGlzIG9uIHRoZSB4IGF4aXMgYW5kIHN0cmV0Y2ggdGhvc2UgYmFycyB0byBlYWNoIGFkZCB1cCB0byBhIHRvdGFsIHByb3BvcnRpb24gb2YgMSwgc28gd2UgYWRkIHRoZSBwb3NpdGlvbiBlcXVhbHMgZmlsbCBvcHRpb24gdG8gdGhlIGdlb20gYmFyIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCmdncGxvdChjb21pY3MsIGFlcyhpZCwgZmlsbCA9IGFsaWduKSkgKyBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKyANCiAgeWxhYigiUHJvcG9ydGlvbiIpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoY29taWNzLCBhZXMoYWxpZ24sIGZpbGwgPSBpZCkpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsNCiAgeWxhYigiUHJvcG9ydGlvbiIpDQpgYGANCg0KYGBge3J9DQp0YWIgPC0gdGFibGUoY29taWNzJGFsaWduLCBjb21pY3MkZ2VuZGVyKQ0Kb3B0aW9ucyhzY2lwZW4gPSA5OTksIGRpZ2l0cyA9IDMpDQpwcm9wLnRhYmxlKHRhYikNCnByb3AudGFibGUodGFiLCAyKQ0KYGBgDQoNCiMjIyAqKlBsb3Qgb2YgZ2VuZGVyIGJ5IGFsaWduKioNCg0KYGBge3J9DQpnZ3Bsb3QoY29taWNzX2ZpbHRlcmVkLCBhZXMoYWxpZ24sIGZpbGwgPSBnZW5kZXIpKSArIA0KICBnZW9tX2JhcigpDQpgYGANCg0KIyMjICoqUGxvdCBwcm9wb3J0aW9uIG9mIGdlbmRlciwgY29uZGl0aW9uYWwgb24gYWxpZ24qKg0KDQpgYGB7cn0NCmdncGxvdChjb21pY3NfZmlsdGVyZWQsIGFlcyhhbGlnbiwgZmlsbCA9IGdlbmRlcikpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsNCiAgeWxhYigicHJvcG9ydGlvbiIpDQpgYGANCg0KIyMgKipEaXN0cmlidXRpb24gb2Ygb25lIHZhcmlhYmxlKioNCg0KIyMjICoqTWFyZ2luYWwgZGlzdHJpYnV0aW9uKioNCg0KVG8gY29tcHV0ZSBhIHRhYmxlIG9mIGNvdW50cyBmb3IgYSBzaW5nbGUgdmFyaWFibGUgbGlrZSBpZCwganVzdCBwcm92aWRlIHZlY3RvciBpbnRvIGludG8gdGhlIHRhYmxlIGZ1bmN0aW9uIGJ5IHRoZSBzb2xlIGFyZ3VtZW50LiBPbmUgd2F5IHRvIHRoaW5rIG9mIHdoYXQgd2UndmUgZG9uZSBpcyB0byB0YWtlIHRoZSBvcmlnaW5hbCB0d28td2F5IHRhYmxlIGFuZCB0aGVuLCBzdW0gdGhlIGNlbGxzIGFjcm9zcyBlYWNoIGxldmVsIG9mIGFsaWduLiBTaW5jZSB3ZSd2ZSBzdW1tZWQgb3ZlciB0aGUgbWFyZ2lucyBvZiB0aGUgb3RoZXIgdmFyaWFibGVzLCB0aGlzIGlzIHNvbWV0aW1lcyBrbm93biBhcyBhIG1hcmdpbmFsIGRpc3RyaWJ1dGlvbi4NCg0KYGBge3J9DQp0YWJsZShjb21pY3MkaWQpDQpgYGANCg0KYGBge3J9DQp0YWJfY250IDwtIHRhYmxlKGNvbWljcyRpZCwgY29taWNzJGFsaWduKQ0KdGFiX2NudA0KYGBgDQoNCiMjIyAqKlNpbXBsZSBiYXIgY2hhcnQqKg0KDQpUaGUgc3ludGF4IHRvIGNyZWF0ZSB0aGUgc2ltcGxlIGJhciBjaGFydCBpcyBzdHJhaWdodGZvcndhcmQgYXMgd2VsbCwganVzdCByZW1vdmUgdGhlIGZpbGwgZXF1YWxzIGFsaWduIGFyZ3VtZW50Lg0KDQpgYGB7cn0NCmdncGxvdChjb21pY3MsIGFlcyhpZCkpICsgDQogIGdlb21fYmFyKCkNCmBgYA0KDQojIyMgKipGYWNldGluZyoqDQoNCkFub3RoZXIgdXNlZnVsIHdheSB0byBmb3JtIHRoZSBkaXN0cmlidXRpb24gb2YgYSBzaW5nbGUgdmFyaWFibGUgaXMgdG8gY29uZGl0aW9uIG9uIGEgcGFydGljdWxhciB2YWx1ZSBvZiBhbm90aGVyIHZhcmlhYmxlLiBXZSBtaWdodCBiZSBpbnRlcmVzdGVkLCBmb3IgZXhhbXBsZSwgaW4gdGhlIGRpc3RyaWJ1dGlvbiBvZiAqKmlkKiogZm9yIGFsbCAqKm5ldXRyYWwgY2hhcmFjdGVycyoqLiBXZSBjb3VsZCBlaXRoZXIgZmlsdGVyIHRoZSBkYXRhc2V0IGFuZCBidWlsZCBhIGJhciBjaGFydCB1c2luZyBvbmx5IGNhc2VzIHdoZXJlIGFsaWdubWVudCB3YXMgbmV1dHJhbCwgb3Igd2UgY291bGQgdXNlIGEgdGVjaG5pcXVlIGNhbGxlZCBmYWNldGluZy4gRmFjZXRpbmcgYnJlYWtzIHRoZSBkYXRhIGludG8gc3Vic2V0cyBiYXNlZCBvbiB0aGUgbGV2ZWxzIG9mIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgYW5kIHRoZW4gY29uc3RydWN0cyBhIHBsb3QgZm9yIGVhY2guDQoNCiMjIyAqKkZhY2V0ZWQgYmFyIGNoYXJ0cyoqDQoNClRvIGltcGxlbWVudCB0aGlzIGluIGBnZ3Bsb3QyYCwgd2UganVzdCBuZWVkIHRvIGFkZCBhIGZhY2V0aW5nIGxheWVyOiB0aGUgYGZhY2V0X3dyYXAoKWAgZnVuY3Rpb24sIHRoZW4gYSB0aWxkZSAoXH4pLCB3aGljaCBjYW4gYmUgcmVhZCBhcyAiKipicm9rZW4gZG93biBieSoqIiBhbmQgdGhlbiBvdXIgdmFyaWFibGUgYGFsaWduYC4gVGhlIHJlc3VsdCBpcyB0aHJlZSBzaW1wbGUgYmFyIGNoYXJ0cyBzaWRlLWJ5LXNpZGUsIHRoZSBmaXJzdCBvbmUgY29ycmVzcG9uZGluZyB0byB0aGUgZGlzdHJpYnV0aW9uIG9mIGlkIHdpdGhpbiBhbGwgY2FzZXMgdGhhdCBoYXZlIGEgYmFkIGFsaWdubWVudCwgYW5kIHNvIG9uLCBmb3IgZ29vZCBhbmQgbmV1dHJhbCBhbGlnbm1lbnRzLiBJZiB0aGlzIHBsb3QgZmVlbHMgZmFtaWxpYXIsIGl0IHNob3VsZC4NCg0KYGBge3J9DQpnZ3Bsb3QoY29taWNzX2ZpbHRlcmVkLCBhZXMoaWQpKSArIGdlb21fYmFyKCkgKyANCiAgZmFjZXRfd3JhcCh+IGFsaWduKQ0KYGBgDQoNCiMjIyAqKk1hcmdpbmFsIGJhciBjaGFydCoqDQoNCklmIHlvdSBhcmUgaW50ZXJlc3RlZCBpbiB0aGUgZGlzdHJpYnV0aW9uIG9mIGFsaWdubWVudCBvZiAqYWxsKiBzdXBlcmhlcm9lcywgaXQgbWFrZXMgc2Vuc2UgdG8gY29uc3RydWN0IGEgYmFyIGNoYXJ0IGZvciBqdXN0IHRoYXQgc2luZ2xlIHZhcmlhYmxlLg0KDQpZb3UgY2FuIGltcHJvdmUgdGhlIGludGVycHJldGFiaWxpdHkgb2YgdGhlIHBsb3QsIHRob3VnaCwgYnkgaW1wbGVtZW50aW5nIHNvbWUgc2Vuc2libGUgb3JkZXJpbmcuIFN1cGVyaGVyb2VzIHRoYXQgYXJlIGAiTmV1dHJhbCJgIHNob3cgYW4gYWxpZ25tZW50IGJldHdlZW4gYCJHb29kImAgYW5kIGAiQmFkImAsIHNvIGl0IG1ha2VzIHNlbnNlIHRvIHB1dCB0aGF0IGJhciBpbiB0aGUgbWlkZGxlLg0KDQpgYGB7cn0NCmNvbWljcyRhbGlnbiA8LSBmYWN0b3IoY29taWNzJGFsaWduLCBsZXZlbHMgPSBjKCJCYWQiLCAiTmV1dHJhbCIsICJHb29kIikpDQoNCiMgQ3JlYXRlIHRoZSBwbG90IG9mIGFsaWduDQpnZ3Bsb3QoY29taWNzLCBhZXMoYWxpZ24pKSArIGdlb21fYmFyKCkNCmBgYA0KDQojIyMgKipDb25kaXRpb25hbCBiYXIgY2hhcnQqKg0KDQpOb3csIGlmIHlvdSB3YW50IHRvIGJyZWFrIGRvd24gdGhlIGRpc3RyaWJ1dGlvbiBvZiBhbGlnbm1lbnQgYmFzZWQgb24gZ2VuZGVyLCB5b3UncmUgbG9va2luZyBmb3IgY29uZGl0aW9uYWwgZGlzdHJpYnV0aW9ucy4NCg0KWW91IGNvdWxkIG1ha2UgdGhlc2UgYnkgY3JlYXRpbmcgbXVsdGlwbGUgZmlsdGVyZWQgZGF0YXNldHMgKG9uZSBmb3IgZWFjaCBnZW5kZXIpIG9yIGJ5IGZhY2V0aW5nIHRoZSBwbG90IG9mIGFsaWdubWVudCBiYXNlZCBvbiBnZW5kZXIuIEFzIGEgcG9pbnQgb2YgY29tcGFyaXNvbiwgd2UndmUgcHJvdmlkZWQgeW91ciBwbG90IG9mIHRoZSBtYXJnaW5hbCBkaXN0cmlidXRpb24gb2YgYWxpZ25tZW50IGZyb20gdGhlIGxhc3QgZXhlcmNpc2UuDQoNCmBgYHtyfQ0KZ2dwbG90KGNvbWljc19maWx0ZXJlZCwgYWVzKGFsaWduKSkgKyBnZW9tX2JhcigpICsgDQogIGZhY2V0X3dyYXAofiBnZW5kZXIpDQpgYGANCg0KIyMjICoqSW1wcm92ZSBwaWUgY2hhcnQqKg0KDQpUaGUgcGllIGNoYXJ0IGlzIGEgdmVyeSBjb21tb24gd2F5IHRvIHJlcHJlc2VudCB0aGUgZGlzdHJpYnV0aW9uIG9mIGEgc2luZ2xlIGNhdGVnb3JpY2FsIHZhcmlhYmxlLCBidXQgdGhleSBjYW4gYmUgbW9yZSBkaWZmaWN1bHQgdG8gaW50ZXJwcmV0IHRoYW4gYmFyIGNoYXJ0cy4NCg0KYGBge3J9DQpnZ3Bsb3QoY29taWNzLCBhZXMoYWxpZ24pKSArDQogIGdlb21fYmFyKGZpbGwgPSAiY2hhcnRyZXVzZSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpDQpgYGANCg0KIyMjICoqRU5EKioNCg==