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==