Data Visualization in ggplot

Penn SAS Data Driven Discovery Summer Hangouts 2023

Brynn Sherman ()

DDDI postdoctoral fellow | Department of Psychology

Load in the relevant packages

library(tidyverse)
library(datasets)

Check out the Iris dataset

?iris
iris

Plot sepal length and sepal width against one another

ggplot(data = iris, aes(x = Sepal.Length, y = Sepal.Width)) + geom_point()

Add a line of best fit

ggplot(data = iris, aes(x = Sepal.Length, y = Sepal.Width)) + geom_point() + geom_smooth(method = "lm")
`geom_smooth()` using formula 'y ~ x'

Let’s separate out based on species

First, by color:

ggplot(data = iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) + geom_point() + geom_smooth(method = "lm")
`geom_smooth()` using formula 'y ~ x'

Next, by facets

ggplot(data = iris, aes(x = Sepal.Length, y = Sepal.Width)) + geom_point() + geom_smooth(method = "lm") + facet_wrap(~Species)
`geom_smooth()` using formula 'y ~ x'

Note that by default, R has the scale of the three subplots as the same. How can we change that?

ggplot(data = iris, aes(x = Sepal.Length, y = Sepal.Width)) + geom_point() + geom_smooth(method = "lm") + facet_wrap(~Species, scales = "free")
`geom_smooth()` using formula 'y ~ x'

We can also change the shape of the points associated with the three species

ggplot(data = iris, aes(x = Sepal.Length, y = Sepal.Width, shape = Species)) + geom_point() + geom_smooth(method = "lm")
`geom_smooth()` using formula 'y ~ x'

ggplot(data = iris, aes(x = Sepal.Length, y = Sepal.Width, shape = Species)) + geom_point(alpha = .25) + geom_smooth(method = "lm")
`geom_smooth()` using formula 'y ~ x'

Plotting the average petal length for each species

groupedData = group_by(iris, Species) %>% summarise(meanPetalLength = mean(Petal.Length))
groupedData
ggplot(data = groupedData,aes(x = Species, y = meanPetalLength)) + geom_bar(stat = "identity")

The bars represent the means, which isn’t the most useful. Ideally, we’d also like a measure of variance.

One way to do this is to add error bars (in this case, standard error of the mean)

groupedData = group_by(iris, Species) %>% summarise(meanPetalLength = mean(Petal.Length),sdPetalLength = sd(Petal.Length)/sqrt(n()))
groupedData
ggplot(data = groupedData,aes(x = Species, y = meanPetalLength)) + geom_bar(stat = "identity") + geom_errorbar(data = groupedData,aes(x = Species, ymin = meanPetalLength - sdPetalLength, ymax = meanPetalLength + sdPetalLength))

ggplot(data = groupedData,aes(x = Species, y = meanPetalLength)) + geom_bar(stat = "identity") + geom_errorbar(data = groupedData,aes(x = Species, ymin = meanPetalLength - sdPetalLength, ymax = meanPetalLength + sdPetalLength),width=.1)

Aside: Another way to plot the mean more directly (without creating a new dataframe)

ggplot(data = iris,aes(x = Species, y = Petal.Length)) + geom_bar(stat = "summary", fun.y = "mean")
Warning: Ignoring unknown parameters: fun.y
No summary function supplied, defaulting to `mean_se()`

What if we want to get a better sense of the distribution of petal lengths for each species (not just the mean/sd)?

Histograms

ggplot(data = iris, aes(x = Petal.Length,fill = Species)) + geom_histogram()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

ggplot(data = iris, aes(x = Petal.Length)) + geom_histogram() + facet_wrap(~Species)
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Boxplot

ggplot(data = iris, aes(x = Species, y = Petal.Length)) + geom_boxplot() 

Dotplot

ggplot(data = iris, aes(x = Species, y = Petal.Length)) + geom_dotplot(binaxis = "y",stackdir = "center")
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.

ggplot(data = iris, aes(x = Species, y = Petal.Length)) + geom_dotplot(binaxis = "y",stackdir = "center",dotsize=.5)
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.

Violin plot

ggplot(data = iris, aes(x = Species, y = Petal.Length)) + geom_violin()

Violin + boxplot

ggplot(data = iris, aes(x = Species, y = Petal.Length)) + geom_violin() + geom_dotplot(binaxis = "y",stackdir = "center",dotsize=.5,alpha=.5)
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.

Plotting the individual data points on the mean bars

groupedData
ggplot(data = groupedData,aes(x = Species, y = meanPetalLength)) + geom_bar(stat = "identity") + geom_dotplot(data = iris, aes(x = Species, y = Petal.Length), binaxis = "y", stackdir = "center",dotsize = .5)
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.

Now let’s spruce up the graph

First fix the y axis label

ggplot(data = groupedData,aes(x = Species, y = meanPetalLength)) + geom_bar(stat = "identity") + geom_dotplot(data = iris, aes(x = Species, y = Petal.Length), binaxis = "y", stackdir = "center",dotsize = .5) + ylab('Petal Length')
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.

Change the theme

ggplot(data = groupedData,aes(x = Species, y = meanPetalLength)) + geom_bar(stat = "identity") + geom_dotplot(data = iris, aes(x = Species, y = Petal.Length), binaxis = "y", stackdir = "center",dotsize = .5) + ylab('Petal Length') + theme_classic()
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.

Color by species

ggplot(data = groupedData,aes(x = Species, y = meanPetalLength, fill = Species)) + geom_bar(stat = "identity") + geom_dotplot(data = iris, aes(x = Species, y = Petal.Length), binaxis = "y", stackdir = "center",dotsize = .5) + ylab('Petal Length') + theme_classic()
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.

Change the color scheme

library(RColorBrewer)
ggplot(data = groupedData,aes(x = Species, y = meanPetalLength, fill = Species)) + geom_bar(stat = "identity") + geom_dotplot(data = iris, aes(x = Species, y = Petal.Length), binaxis = "y", stackdir = "center",dotsize = .5) + ylab('Petal Length') + theme_classic() + scale_fill_brewer(palette = "Accent")
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.

Change the font, font size

ggplot(data = groupedData,aes(x = Species, y = meanPetalLength, fill = Species)) + geom_bar(stat = "identity") + geom_dotplot(data = iris, aes(x = Species, y = Petal.Length), binaxis = "y", stackdir = "center",dotsize = .5) + ylab('Petal Length') + theme_classic() + scale_fill_brewer(palette = "Accent") + theme(text = element_text(size = 20,family = "mono"))
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.
# save the plot
ggsave('petal_means.pdf',width=5,height=5)
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.

Change the size of the plot so that it’s not cut off when it saves

ggplot(data = groupedData,aes(x = Species, y = meanPetalLength, fill = Species)) + geom_bar(stat = "identity") + geom_dotplot(data = iris, aes(x = Species, y = Petal.Length), binaxis = "y", stackdir = "center",dotsize = .5) + ylab('Petal Length') + theme_classic() + scale_fill_brewer(palette = "Accent") + theme(text = element_text(size = 20,family = "mono"))
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.
# save the plot
ggsave('petal_means_wide.pdf',width=7,height=5)
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.

Alternatively: is a legend really necessary in this plot?

ggplot(data = groupedData,aes(x = Species, y = meanPetalLength, fill = Species)) + geom_bar(stat = "identity") + geom_dotplot(data = iris, aes(x = Species, y = Petal.Length), binaxis = "y", stackdir = "center",dotsize = .5) + ylab('Petal Length') + theme_classic() + scale_fill_brewer(palette = "Accent") + theme(text = element_text(size = 20,family = "mono")) + theme(legend.position = "none")
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.
# save the plot
ggsave('petal_means_noLegend.pdf',width=5,height=5)
Bin width defaults to 1/30 of the range of the data. Pick better value with `binwidth`.

Load in a different dataset (mpg) to illustrate a few other specific instances that come up often

?mpg
mpg

How do highway and city mpg relate to one another, and is there an effect of the model year?

ggplot(data = mpg,aes(x = cty, y = hwy, color = cyl)) + geom_point() + geom_smooth(method = "lm")
`geom_smooth()` using formula 'y ~ x'

The above graph treats cylinder as a continuous variable (which is probably okay in this case). But what if it is discrete variable?

ggplot(data = mpg,aes(x = cty, y = hwy, color = as.factor(cyl))) + geom_point() + geom_smooth(method = "lm")
`geom_smooth()` using formula 'y ~ x'

# alternatively, could also change it within the dataframe:
# mpg$cyl = factor(mpg$cyl)

Changing it to a factor means that we get individual lines of best fit for each level of the variable. Here’s a work-around to avoid that

ggplot(data = mpg,aes(x = cty, y = hwy)) + geom_point(aes(color = as.factor(cyl))) + geom_smooth(method = "lm")
`geom_smooth()` using formula 'y ~ x'

mpg

What is the average highway mpg by year for each manufacturer?

groupedData = group_by(mpg, manufacturer, year) %>% summarise(meanMPG = mean(hwy),sdMPG = sd(hwy)/sqrt(n()))
`summarise()` has grouped output by 'manufacturer'. You can override using the `.groups` argument.
groupedData

For the sake of space, let’s only plot Audi, Chevy, Dodge, Ford, and Honda

filteredData = filter(groupedData,manufacturer %in% c("audi","chevrolet","dodge","ford","honda"))
filteredData
ggplot(data = filteredData,aes(x = manufacturer, y = meanMPG, fill = year)) + geom_bar(stat = "identity") + geom_errorbar(data = filteredData,aes(x = manufacturer, ymin = meanMPG - sdMPG, ymax = meanMPG + sdMPG),width=.5)

Unstack the bars

ggplot(data = filteredData,aes(x = manufacturer, y = meanMPG, fill = year)) + geom_bar(stat = "identity", position = "dodge") + geom_errorbar(data = filteredData,aes(x = manufacturer, ymin = meanMPG - sdMPG, ymax = meanMPG + sdMPG),width=.5, position = position_dodge(.9))

Why did that not work?

ggplot(data = filteredData,aes(x = manufacturer, y = meanMPG, fill = as.factor(year))) + geom_bar(stat = "identity", position = "dodge") + geom_errorbar(data = filteredData,aes(x = manufacturer, ymin = meanMPG - sdMPG, ymax = meanMPG + sdMPG),width=.5, position = position_dodge(.9))

Make generative art! Courtesy of https://r-graph-gallery.com/137-spring-shapes-data-art.html

set.seed(567)
ngroup=30
names=paste("G_",seq(1,ngroup),sep="")
DAT=data.frame()

for(i in seq(1:30)){
    data=data.frame( matrix(0, ngroup , 3))
    data[,1]=i
    data[,2]=sample(names, nrow(data))
    data[,3]=prop.table(sample( c(rep(0,100),c(1:ngroup)) ,nrow(data)))
    DAT=rbind(DAT,data)
    }
colnames(DAT)=c("Year","Group","Value")
DAT=DAT[order( DAT$Year, DAT$Group) , ]


coul = brewer.pal(12, "Paired") 
coul = colorRampPalette(coul)(ngroup)
coul=coul[sample(c(1:length(coul)) , size=length(coul) ) ]

ggplot(DAT, aes(x=Year, y=Value, fill=Group )) + 
    geom_area(alpha=1  )+
    theme_bw() +
    #scale_fill_brewer(colour="red", breaks=rev(levels(DAT$Group)))+
    scale_fill_manual(values = coul)+
     theme(
        text = element_blank(),
        line = element_blank(),
        title = element_blank(),
        legend.position="none",
        panel.border = element_blank(),
        panel.background = element_blank())
ggsave('generative_art.pdf',width=7,height=5)

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCgotLS0KCiMgRGF0YSBWaXN1YWxpemF0aW9uIGluIGdncGxvdAoKIyBQZW5uIFNBUyBEYXRhIERyaXZlbiBEaXNjb3ZlcnkgU3VtbWVyIEhhbmdvdXRzIDIwMjMKCiMjIyBCcnlubiBTaGVybWFuIChicnlubnNAc2FzLnVwZW5uLmVkdSkKCiMjIyBERERJIHBvc3Rkb2N0b3JhbCBmZWxsb3cgfCBEZXBhcnRtZW50IG9mIFBzeWNob2xvZ3kKCkxvYWQgaW4gdGhlIHJlbGV2YW50IHBhY2thZ2VzCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZGF0YXNldHMpCmBgYAoKQ2hlY2sgb3V0IHRoZSBJcmlzIGRhdGFzZXQKYGBge3J9Cj9pcmlzCmBgYAoKYGBge3J9CmlyaXMKYGBgClBsb3Qgc2VwYWwgbGVuZ3RoIGFuZCBzZXBhbCB3aWR0aCBhZ2FpbnN0IG9uZSBhbm90aGVyCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGlyaXMsIGFlcyh4ID0gU2VwYWwuTGVuZ3RoLCB5ID0gU2VwYWwuV2lkdGgpKSArIGdlb21fcG9pbnQoKQpgYGAKQWRkIGEgbGluZSBvZiBiZXN0IGZpdAoKYGBge3J9CmdncGxvdChkYXRhID0gaXJpcywgYWVzKHggPSBTZXBhbC5MZW5ndGgsIHkgPSBTZXBhbC5XaWR0aCkpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikKYGBgCkxldCdzIHNlcGFyYXRlIG91dCBiYXNlZCBvbiBzcGVjaWVzIAoKRmlyc3QsIGJ5IGNvbG9yOgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBpcmlzLCBhZXMoeCA9IFNlcGFsLkxlbmd0aCwgeSA9IFNlcGFsLldpZHRoLCBjb2xvciA9IFNwZWNpZXMpKSArIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpCmBgYApOZXh0LCBieSBmYWNldHMKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGlyaXMsIGFlcyh4ID0gU2VwYWwuTGVuZ3RoLCB5ID0gU2VwYWwuV2lkdGgpKSArIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsgZmFjZXRfd3JhcCh+U3BlY2llcykKYGBgCk5vdGUgdGhhdCBieSBkZWZhdWx0LCBSIGhhcyB0aGUgc2NhbGUgb2YgdGhlIHRocmVlIHN1YnBsb3RzIGFzIHRoZSBzYW1lLiBIb3cgY2FuIHdlIGNoYW5nZSB0aGF0PwpgYGB7cn0KZ2dwbG90KGRhdGEgPSBpcmlzLCBhZXMoeCA9IFNlcGFsLkxlbmd0aCwgeSA9IFNlcGFsLldpZHRoKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKSArIGZhY2V0X3dyYXAoflNwZWNpZXMsIHNjYWxlcyA9ICJmcmVlIikKYGBgCldlIGNhbiBhbHNvIGNoYW5nZSB0aGUgc2hhcGUgb2YgdGhlIHBvaW50cyBhc3NvY2lhdGVkIHdpdGggdGhlIHRocmVlIHNwZWNpZXMKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGlyaXMsIGFlcyh4ID0gU2VwYWwuTGVuZ3RoLCB5ID0gU2VwYWwuV2lkdGgsIHNoYXBlID0gU3BlY2llcykpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikKYGBgCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGlyaXMsIGFlcyh4ID0gU2VwYWwuTGVuZ3RoLCB5ID0gU2VwYWwuV2lkdGgsIHNoYXBlID0gU3BlY2llcykpICsgZ2VvbV9wb2ludChhbHBoYSA9IC4yNSkgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKQpgYGAKUGxvdHRpbmcgdGhlIGF2ZXJhZ2UgcGV0YWwgbGVuZ3RoIGZvciBlYWNoIHNwZWNpZXMKCmBgYHtyfQpncm91cGVkRGF0YSA9IGdyb3VwX2J5KGlyaXMsIFNwZWNpZXMpICU+JSBzdW1tYXJpc2UobWVhblBldGFsTGVuZ3RoID0gbWVhbihQZXRhbC5MZW5ndGgpKQpncm91cGVkRGF0YQpgYGAKYGBge3J9CmdncGxvdChkYXRhID0gZ3JvdXBlZERhdGEsYWVzKHggPSBTcGVjaWVzLCB5ID0gbWVhblBldGFsTGVuZ3RoKSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikKYGBgClRoZSBiYXJzIHJlcHJlc2VudCB0aGUgbWVhbnMsIHdoaWNoIGlzbid0IHRoZSBtb3N0IHVzZWZ1bC4gSWRlYWxseSwgd2UnZCBhbHNvIGxpa2UgYSBtZWFzdXJlIG9mIHZhcmlhbmNlLgoKT25lIHdheSB0byBkbyB0aGlzIGlzIHRvIGFkZCBlcnJvciBiYXJzIChpbiB0aGlzIGNhc2UsIHN0YW5kYXJkIGVycm9yIG9mIHRoZSBtZWFuKQoKYGBge3J9Cmdyb3VwZWREYXRhID0gZ3JvdXBfYnkoaXJpcywgU3BlY2llcykgJT4lIHN1bW1hcmlzZShtZWFuUGV0YWxMZW5ndGggPSBtZWFuKFBldGFsLkxlbmd0aCksc2RQZXRhbExlbmd0aCA9IHNkKFBldGFsLkxlbmd0aCkvc3FydChuKCkpKQpncm91cGVkRGF0YQpgYGAKYGBge3J9CmdncGxvdChkYXRhID0gZ3JvdXBlZERhdGEsYWVzKHggPSBTcGVjaWVzLCB5ID0gbWVhblBldGFsTGVuZ3RoKSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyBnZW9tX2Vycm9yYmFyKGRhdGEgPSBncm91cGVkRGF0YSxhZXMoeCA9IFNwZWNpZXMsIHltaW4gPSBtZWFuUGV0YWxMZW5ndGggLSBzZFBldGFsTGVuZ3RoLCB5bWF4ID0gbWVhblBldGFsTGVuZ3RoICsgc2RQZXRhbExlbmd0aCkpCmBgYApgYGB7cn0KZ2dwbG90KGRhdGEgPSBncm91cGVkRGF0YSxhZXMoeCA9IFNwZWNpZXMsIHkgPSBtZWFuUGV0YWxMZW5ndGgpKSArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIGdlb21fZXJyb3JiYXIoZGF0YSA9IGdyb3VwZWREYXRhLGFlcyh4ID0gU3BlY2llcywgeW1pbiA9IG1lYW5QZXRhbExlbmd0aCAtIHNkUGV0YWxMZW5ndGgsIHltYXggPSBtZWFuUGV0YWxMZW5ndGggKyBzZFBldGFsTGVuZ3RoKSx3aWR0aD0uMSkKYGBgCkFzaWRlOiBBbm90aGVyIHdheSB0byBwbG90IHRoZSBtZWFuIG1vcmUgZGlyZWN0bHkgKHdpdGhvdXQgY3JlYXRpbmcgYSBuZXcgZGF0YWZyYW1lKQoKYGBge3J9CmdncGxvdChkYXRhID0gaXJpcyxhZXMoeCA9IFNwZWNpZXMsIHkgPSBQZXRhbC5MZW5ndGgpKSArIGdlb21fYmFyKHN0YXQgPSAic3VtbWFyeSIsIGZ1bi55ID0gIm1lYW4iKQpgYGAKCgpXaGF0IGlmIHdlIHdhbnQgdG8gZ2V0IGEgYmV0dGVyIHNlbnNlIG9mIHRoZSBkaXN0cmlidXRpb24gb2YgcGV0YWwgbGVuZ3RocyBmb3IgZWFjaCBzcGVjaWVzIChub3QganVzdCB0aGUgbWVhbi9zZCk/IAoKSGlzdG9ncmFtcwpgYGB7cn0KZ2dwbG90KGRhdGEgPSBpcmlzLCBhZXMoeCA9IFBldGFsLkxlbmd0aCxmaWxsID0gU3BlY2llcykpICsgZ2VvbV9oaXN0b2dyYW0oKQpgYGAKYGBge3J9CmdncGxvdChkYXRhID0gaXJpcywgYWVzKHggPSBQZXRhbC5MZW5ndGgpKSArIGdlb21faGlzdG9ncmFtKCkgKyBmYWNldF93cmFwKH5TcGVjaWVzKQpgYGAKCkJveHBsb3QKYGBge3J9CmdncGxvdChkYXRhID0gaXJpcywgYWVzKHggPSBTcGVjaWVzLCB5ID0gUGV0YWwuTGVuZ3RoKSkgKyBnZW9tX2JveHBsb3QoKSAKYGBgCkRvdHBsb3QKYGBge3J9CmdncGxvdChkYXRhID0gaXJpcywgYWVzKHggPSBTcGVjaWVzLCB5ID0gUGV0YWwuTGVuZ3RoKSkgKyBnZW9tX2RvdHBsb3QoYmluYXhpcyA9ICJ5IixzdGFja2RpciA9ICJjZW50ZXIiKQpgYGAKYGBge3J9CmdncGxvdChkYXRhID0gaXJpcywgYWVzKHggPSBTcGVjaWVzLCB5ID0gUGV0YWwuTGVuZ3RoKSkgKyBnZW9tX2RvdHBsb3QoYmluYXhpcyA9ICJ5IixzdGFja2RpciA9ICJjZW50ZXIiLGRvdHNpemU9LjUpCmBgYApWaW9saW4gcGxvdApgYGB7cn0KZ2dwbG90KGRhdGEgPSBpcmlzLCBhZXMoeCA9IFNwZWNpZXMsIHkgPSBQZXRhbC5MZW5ndGgpKSArIGdlb21fdmlvbGluKCkKYGBgClZpb2xpbiArIGJveHBsb3QKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGlyaXMsIGFlcyh4ID0gU3BlY2llcywgeSA9IFBldGFsLkxlbmd0aCkpICsgZ2VvbV92aW9saW4oKSArIGdlb21fZG90cGxvdChiaW5heGlzID0gInkiLHN0YWNrZGlyID0gImNlbnRlciIsZG90c2l6ZT0uNSxhbHBoYT0uNSkKYGBgClBsb3R0aW5nIHRoZSBpbmRpdmlkdWFsIGRhdGEgcG9pbnRzIG9uIHRoZSBtZWFuIGJhcnMKCmBgYHtyfQpncm91cGVkRGF0YQpgYGAKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGdyb3VwZWREYXRhLGFlcyh4ID0gU3BlY2llcywgeSA9IG1lYW5QZXRhbExlbmd0aCkpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgZ2VvbV9kb3RwbG90KGRhdGEgPSBpcmlzLCBhZXMoeCA9IFNwZWNpZXMsIHkgPSBQZXRhbC5MZW5ndGgpLCBiaW5heGlzID0gInkiLCBzdGFja2RpciA9ICJjZW50ZXIiLGRvdHNpemUgPSAuNSkKYGBgCk5vdyBsZXQncyBzcHJ1Y2UgdXAgdGhlIGdyYXBoCgpGaXJzdCBmaXggdGhlIHkgYXhpcyBsYWJlbApgYGB7cn0KZ2dwbG90KGRhdGEgPSBncm91cGVkRGF0YSxhZXMoeCA9IFNwZWNpZXMsIHkgPSBtZWFuUGV0YWxMZW5ndGgpKSArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIGdlb21fZG90cGxvdChkYXRhID0gaXJpcywgYWVzKHggPSBTcGVjaWVzLCB5ID0gUGV0YWwuTGVuZ3RoKSwgYmluYXhpcyA9ICJ5Iiwgc3RhY2tkaXIgPSAiY2VudGVyIixkb3RzaXplID0gLjUpICsgeWxhYignUGV0YWwgTGVuZ3RoJykKYGBgCgpDaGFuZ2UgdGhlIHRoZW1lCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGdyb3VwZWREYXRhLGFlcyh4ID0gU3BlY2llcywgeSA9IG1lYW5QZXRhbExlbmd0aCkpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgZ2VvbV9kb3RwbG90KGRhdGEgPSBpcmlzLCBhZXMoeCA9IFNwZWNpZXMsIHkgPSBQZXRhbC5MZW5ndGgpLCBiaW5heGlzID0gInkiLCBzdGFja2RpciA9ICJjZW50ZXIiLGRvdHNpemUgPSAuNSkgKyB5bGFiKCdQZXRhbCBMZW5ndGgnKSArIHRoZW1lX2NsYXNzaWMoKQpgYGAKCkNvbG9yIGJ5IHNwZWNpZXMKYGBge3J9CmdncGxvdChkYXRhID0gZ3JvdXBlZERhdGEsYWVzKHggPSBTcGVjaWVzLCB5ID0gbWVhblBldGFsTGVuZ3RoLCBmaWxsID0gU3BlY2llcykpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgZ2VvbV9kb3RwbG90KGRhdGEgPSBpcmlzLCBhZXMoeCA9IFNwZWNpZXMsIHkgPSBQZXRhbC5MZW5ndGgpLCBiaW5heGlzID0gInkiLCBzdGFja2RpciA9ICJjZW50ZXIiLGRvdHNpemUgPSAuNSkgKyB5bGFiKCdQZXRhbCBMZW5ndGgnKSArIHRoZW1lX2NsYXNzaWMoKQpgYGAKCkNoYW5nZSB0aGUgY29sb3Igc2NoZW1lCgpgYGB7cn0KbGlicmFyeShSQ29sb3JCcmV3ZXIpCmBgYAoKYGBge3J9CmdncGxvdChkYXRhID0gZ3JvdXBlZERhdGEsYWVzKHggPSBTcGVjaWVzLCB5ID0gbWVhblBldGFsTGVuZ3RoLCBmaWxsID0gU3BlY2llcykpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgZ2VvbV9kb3RwbG90KGRhdGEgPSBpcmlzLCBhZXMoeCA9IFNwZWNpZXMsIHkgPSBQZXRhbC5MZW5ndGgpLCBiaW5heGlzID0gInkiLCBzdGFja2RpciA9ICJjZW50ZXIiLGRvdHNpemUgPSAuNSkgKyB5bGFiKCdQZXRhbCBMZW5ndGgnKSArIHRoZW1lX2NsYXNzaWMoKSArIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiQWNjZW50IikKYGBgCgpDaGFuZ2UgdGhlIGZvbnQsIGZvbnQgc2l6ZQoKYGBge3J9CmdncGxvdChkYXRhID0gZ3JvdXBlZERhdGEsYWVzKHggPSBTcGVjaWVzLCB5ID0gbWVhblBldGFsTGVuZ3RoLCBmaWxsID0gU3BlY2llcykpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgZ2VvbV9kb3RwbG90KGRhdGEgPSBpcmlzLCBhZXMoeCA9IFNwZWNpZXMsIHkgPSBQZXRhbC5MZW5ndGgpLCBiaW5heGlzID0gInkiLCBzdGFja2RpciA9ICJjZW50ZXIiLGRvdHNpemUgPSAuNSkgKyB5bGFiKCdQZXRhbCBMZW5ndGgnKSArIHRoZW1lX2NsYXNzaWMoKSArIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiQWNjZW50IikgKyB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCxmYW1pbHkgPSAibW9ubyIpKQoKIyBzYXZlIHRoZSBwbG90Cmdnc2F2ZSgncGV0YWxfbWVhbnMucGRmJyx3aWR0aD01LGhlaWdodD01KQpgYGAKQ2hhbmdlIHRoZSBzaXplIG9mIHRoZSBwbG90IHNvIHRoYXQgaXQncyBub3QgY3V0IG9mZiB3aGVuIGl0IHNhdmVzCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGdyb3VwZWREYXRhLGFlcyh4ID0gU3BlY2llcywgeSA9IG1lYW5QZXRhbExlbmd0aCwgZmlsbCA9IFNwZWNpZXMpKSArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIGdlb21fZG90cGxvdChkYXRhID0gaXJpcywgYWVzKHggPSBTcGVjaWVzLCB5ID0gUGV0YWwuTGVuZ3RoKSwgYmluYXhpcyA9ICJ5Iiwgc3RhY2tkaXIgPSAiY2VudGVyIixkb3RzaXplID0gLjUpICsgeWxhYignUGV0YWwgTGVuZ3RoJykgKyB0aGVtZV9jbGFzc2ljKCkgKyBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkFjY2VudCIpICsgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMjAsZmFtaWx5ID0gIm1vbm8iKSkKCiMgc2F2ZSB0aGUgcGxvdApnZ3NhdmUoJ3BldGFsX21lYW5zX3dpZGUucGRmJyx3aWR0aD03LGhlaWdodD01KQpgYGAKCkFsdGVybmF0aXZlbHk6IGlzIGEgbGVnZW5kIHJlYWxseSBuZWNlc3NhcnkgaW4gdGhpcyBwbG90PwoKYGBge3J9CmdncGxvdChkYXRhID0gZ3JvdXBlZERhdGEsYWVzKHggPSBTcGVjaWVzLCB5ID0gbWVhblBldGFsTGVuZ3RoLCBmaWxsID0gU3BlY2llcykpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgZ2VvbV9kb3RwbG90KGRhdGEgPSBpcmlzLCBhZXMoeCA9IFNwZWNpZXMsIHkgPSBQZXRhbC5MZW5ndGgpLCBiaW5heGlzID0gInkiLCBzdGFja2RpciA9ICJjZW50ZXIiLGRvdHNpemUgPSAuNSkgKyB5bGFiKCdQZXRhbCBMZW5ndGgnKSArIHRoZW1lX2NsYXNzaWMoKSArIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiQWNjZW50IikgKyB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCxmYW1pbHkgPSAibW9ubyIpKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCiMgc2F2ZSB0aGUgcGxvdApnZ3NhdmUoJ3BldGFsX21lYW5zX25vTGVnZW5kLnBkZicsd2lkdGg9NSxoZWlnaHQ9NSkKYGBgCkxvYWQgaW4gYSBkaWZmZXJlbnQgZGF0YXNldCAobXBnKSB0byBpbGx1c3RyYXRlIGEgZmV3IG90aGVyIHNwZWNpZmljIGluc3RhbmNlcyB0aGF0IGNvbWUgdXAgb2Z0ZW4KYGBge3J9Cj9tcGcKYGBgCgpgYGB7cn0KbXBnCmBgYApIb3cgZG8gaGlnaHdheSBhbmQgY2l0eSBtcGcgcmVsYXRlIHRvIG9uZSBhbm90aGVyLCBhbmQgaXMgdGhlcmUgYW4gZWZmZWN0IG9mIHRoZSBtb2RlbCB5ZWFyPwpgYGB7cn0KZ2dwbG90KGRhdGEgPSBtcGcsYWVzKHggPSBjdHksIHkgPSBod3ksIGNvbG9yID0gY3lsKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKQpgYGAKVGhlIGFib3ZlIGdyYXBoIHRyZWF0cyBjeWxpbmRlciBhcyBhIGNvbnRpbnVvdXMgdmFyaWFibGUgKHdoaWNoIGlzIHByb2JhYmx5IG9rYXkgaW4gdGhpcyBjYXNlKS4gQnV0IHdoYXQgaWYgaXQgaXMgZGlzY3JldGUgdmFyaWFibGU/CmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZyxhZXMoeCA9IGN0eSwgeSA9IGh3eSwgY29sb3IgPSBhcy5mYWN0b3IoY3lsKSkpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikKCiMgYWx0ZXJuYXRpdmVseSwgY291bGQgYWxzbyBjaGFuZ2UgaXQgd2l0aGluIHRoZSBkYXRhZnJhbWU6CiMgbXBnJGN5bCA9IGZhY3RvcihtcGckY3lsKQpgYGAKQ2hhbmdpbmcgaXQgdG8gYSBmYWN0b3IgbWVhbnMgdGhhdCB3ZSBnZXQgaW5kaXZpZHVhbCBsaW5lcyBvZiBiZXN0IGZpdCBmb3IgZWFjaCBsZXZlbCBvZiB0aGUgdmFyaWFibGUuIEhlcmUncyBhIHdvcmstYXJvdW5kIHRvIGF2b2lkIHRoYXQgCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZyxhZXMoeCA9IGN0eSwgeSA9IGh3eSkpICsgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBhcy5mYWN0b3IoY3lsKSkpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikKYGBgCgpgYGB7cn0KbXBnCmBgYApXaGF0IGlzIHRoZSBhdmVyYWdlIGhpZ2h3YXkgbXBnIGJ5IHllYXIgZm9yIGVhY2ggbWFudWZhY3R1cmVyPwoKYGBge3J9Cmdyb3VwZWREYXRhID0gZ3JvdXBfYnkobXBnLCBtYW51ZmFjdHVyZXIsIHllYXIpICU+JSBzdW1tYXJpc2UobWVhbk1QRyA9IG1lYW4oaHd5KSxzZE1QRyA9IHNkKGh3eSkvc3FydChuKCkpKQpncm91cGVkRGF0YQpgYGAKRm9yIHRoZSBzYWtlIG9mIHNwYWNlLCBsZXQncyBvbmx5IHBsb3QgQXVkaSwgQ2hldnksIERvZGdlLCBGb3JkLCBhbmQgSG9uZGEKYGBge3J9CmZpbHRlcmVkRGF0YSA9IGZpbHRlcihncm91cGVkRGF0YSxtYW51ZmFjdHVyZXIgJWluJSBjKCJhdWRpIiwiY2hldnJvbGV0IiwiZG9kZ2UiLCJmb3JkIiwiaG9uZGEiKSkKZmlsdGVyZWREYXRhCmBgYApgYGB7cn0KZ2dwbG90KGRhdGEgPSBmaWx0ZXJlZERhdGEsYWVzKHggPSBtYW51ZmFjdHVyZXIsIHkgPSBtZWFuTVBHLCBmaWxsID0geWVhcikpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgZ2VvbV9lcnJvcmJhcihkYXRhID0gZmlsdGVyZWREYXRhLGFlcyh4ID0gbWFudWZhY3R1cmVyLCB5bWluID0gbWVhbk1QRyAtIHNkTVBHLCB5bWF4ID0gbWVhbk1QRyArIHNkTVBHKSx3aWR0aD0uNSkKYGBgClVuc3RhY2sgdGhlIGJhcnMKYGBge3J9CmdncGxvdChkYXRhID0gZmlsdGVyZWREYXRhLGFlcyh4ID0gbWFudWZhY3R1cmVyLCB5ID0gbWVhbk1QRywgZmlsbCA9IHllYXIpKSArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsgZ2VvbV9lcnJvcmJhcihkYXRhID0gZmlsdGVyZWREYXRhLGFlcyh4ID0gbWFudWZhY3R1cmVyLCB5bWluID0gbWVhbk1QRyAtIHNkTVBHLCB5bWF4ID0gbWVhbk1QRyArIHNkTVBHKSx3aWR0aD0uNSwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSguOSkpCmBgYAoKV2h5IGRpZCB0aGF0IG5vdCB3b3JrPwoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGZpbHRlcmVkRGF0YSxhZXMoeCA9IG1hbnVmYWN0dXJlciwgeSA9IG1lYW5NUEcsIGZpbGwgPSBhcy5mYWN0b3IoeWVhcikpKSArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsgZ2VvbV9lcnJvcmJhcihkYXRhID0gZmlsdGVyZWREYXRhLGFlcyh4ID0gbWFudWZhY3R1cmVyLCB5bWluID0gbWVhbk1QRyAtIHNkTVBHLCB5bWF4ID0gbWVhbk1QRyArIHNkTVBHKSx3aWR0aD0uNSwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSguOSkpCmBgYAoKTWFrZSBnZW5lcmF0aXZlIGFydCEgQ291cnRlc3kgb2YgaHR0cHM6Ly9yLWdyYXBoLWdhbGxlcnkuY29tLzEzNy1zcHJpbmctc2hhcGVzLWRhdGEtYXJ0Lmh0bWwKCmBgYHtyfQpzZXQuc2VlZCg1NjcpICMgY2hhbmdlIHRoaXMgdG8gZ2VuZXJhdGUgc2xpZ2h0bHkgZGlmZmVyZW50IGltYWdlcwpuZ3JvdXA9MzAKbmFtZXM9cGFzdGUoIkdfIixzZXEoMSxuZ3JvdXApLHNlcD0iIikKREFUPWRhdGEuZnJhbWUoKQoKZm9yKGkgaW4gc2VxKDE6MzApKXsKICAgIGRhdGE9ZGF0YS5mcmFtZSggbWF0cml4KDAsIG5ncm91cCAsIDMpKQogICAgZGF0YVssMV09aQogICAgZGF0YVssMl09c2FtcGxlKG5hbWVzLCBucm93KGRhdGEpKQogICAgZGF0YVssM109cHJvcC50YWJsZShzYW1wbGUoIGMocmVwKDAsMTAwKSxjKDE6bmdyb3VwKSkgLG5yb3coZGF0YSkpKQogICAgREFUPXJiaW5kKERBVCxkYXRhKQogICAgfQpjb2xuYW1lcyhEQVQpPWMoIlllYXIiLCJHcm91cCIsIlZhbHVlIikKREFUPURBVFtvcmRlciggREFUJFllYXIsIERBVCRHcm91cCkgLCBdCgoKY291bCA9IGJyZXdlci5wYWwoMTIsICJQYWlyZWQiKSAKY291bCA9IGNvbG9yUmFtcFBhbGV0dGUoY291bCkobmdyb3VwKQpjb3VsPWNvdWxbc2FtcGxlKGMoMTpsZW5ndGgoY291bCkpICwgc2l6ZT1sZW5ndGgoY291bCkgKSBdCgpnZ3Bsb3QoREFULCBhZXMoeD1ZZWFyLCB5PVZhbHVlLCBmaWxsPUdyb3VwICkpICsgCiAgICBnZW9tX2FyZWEoYWxwaGE9MSAgKSsKICAgIHRoZW1lX2J3KCkgKwogICAgI3NjYWxlX2ZpbGxfYnJld2VyKGNvbG91cj0icmVkIiwgYnJlYWtzPXJldihsZXZlbHMoREFUJEdyb3VwKSkpKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY291bCkrCiAgICAgdGhlbWUoCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBsaW5lID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIsCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpCmdnc2F2ZSgnZ2VuZXJhdGl2ZV9hcnQucGRmJyx3aWR0aD03LGhlaWdodD01KQpgYGAKCg==