版权声明:本套课程材料开源,使用和分享必须遵守「创作共用许可协议 CC BY-NC-SA」(来源引用-非商业用途使用-以相同方式共享)。


Chap11:多图组合

往期要点回顾

本章要点目录

## 本章所需R包
library(bruceR)
# library(ggplot2)  # 加载bruceR时已默认加载ggplot2
# library(cowplot)  # 加载bruceR时已默认加载cowplot

分面小图

【实践1】facet_wrap():拆解分面小图

## 数据准备
data = airquality
data$Month = as.factor(data$Month)
data$Temp.C = (data$Temp - 32) / 1.8  # 摄氏度 = (华氏度 - 32) / 1.8
str(data)
'data.frame':   153 obs. of  7 variables:
 $ Ozone  : int  41 36 12 18 NA 28 23 19 8 NA ...
 $ Solar.R: int  190 118 149 313 NA NA 299 99 19 194 ...
 $ Wind   : num  7.4 8 12.6 11.5 14.3 14.9 8.6 13.8 20.1 8.6 ...
 $ Temp   : int  67 72 74 62 56 66 65 59 61 69 ...
 $ Month  : Factor w/ 5 levels "5","6","7","8",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Day    : int  1 2 3 4 5 6 7 8 9 10 ...
 $ Temp.C : num  19.4 22.2 23.3 16.7 13.3 ...
## 基础散点图
ggplot(data, aes(x=Wind, y=Temp.C)) +
  geom_point()

ggplot(data, aes(x=Wind, y=Temp.C, color=Month)) +
  geom_point()

ggplot(data, aes(x=Wind, y=Temp.C, color=Month)) +
  geom_point() +
  facet_wrap(~ Month)

ggplot(data, aes(x=Wind, y=Temp.C, color=Month)) +
  geom_point() +
  facet_wrap(~ Month, ncol=5)

ggplot(data, aes(x=Wind, y=Temp.C, color=Month)) +
  geom_point(show.legend=FALSE) +
  facet_wrap(~ Month, nrow=1)

ggplot(data, aes(x=Wind, y=Temp.C, color=Month)) +
  geom_point(show.legend=FALSE) +
  geom_smooth(method="lm", se=FALSE, show.legend=FALSE) +
  facet_wrap(~ Month, nrow=1)
`geom_smooth()` using formula = 'y ~ x'

## 修改因子标签
data$Months = factor(
  data$Month,
  levels = 5:9,
  labels = c("May", "June", "July", "August", "September")
)
ggplot(data, aes(x=Wind, y=Temp.C, color=Months)) +
  geom_point(show.legend=FALSE) +
  geom_smooth(method="lm", show.legend=FALSE) +
  facet_wrap(~ Months, nrow=1)
`geom_smooth()` using formula = 'y ~ x'

ggplot(data, aes(x=Wind, y=Temp.C, color=Months)) +
  geom_point(show.legend=FALSE) +
  geom_smooth(method="lm", show.legend=FALSE) +
  facet_wrap(~ Months, nrow=1, scales="free")
`geom_smooth()` using formula = 'y ~ x'

【实践2】facet_grid():两个分类变量的交叉组合分面

  • facet_wrap():线性排列(卷饼🌯)
    • ~ 分组变量
  • facet_grid():两因素交叉组合排列(华夫饼🧇)
    • 行分组变量 ~ 列分组变量

## 两个分类变量的情况
## 数据变量计算复习(Chap 05 & 06)
data.bfi = as.data.table(psych::bfi)
data.bfi[, let(
  Gender = factor(gender, levels=1:2, labels=c("Male", "Female")),
  Age = as.numeric(age),
  Edu = as.factor(education),
  E = MEAN(data.bfi, "E", 1:5, rev=c(1,2), range=1:6),
  A = MEAN(data.bfi, "A", 1:5, rev=1, range=1:6),
  C = MEAN(data.bfi, "C", 1:5, rev=c(4,5), range=1:6),
  N = MEAN(data.bfi, "N", 1:5, rev=NULL, range=1:6),
  O = MEAN(data.bfi, "O", 1:5, rev=c(2,5), range=1:6)
)]
str(data.bfi)
Classes 'data.table' and 'data.frame':  2800 obs. of  36 variables:
 $ A1       : int  2 2 5 4 2 6 2 4 4 2 ...
 $ A2       : int  4 4 4 4 3 6 5 3 3 5 ...
 $ A3       : int  3 5 5 6 3 5 5 1 6 6 ...
 $ A4       : int  4 2 4 5 4 6 3 5 3 6 ...
 $ A5       : int  4 5 4 5 5 5 5 1 3 5 ...
 $ C1       : int  2 5 4 4 4 6 5 3 6 6 ...
 $ C2       : int  3 4 5 4 4 6 4 2 6 5 ...
 $ C3       : int  3 4 4 3 5 6 4 4 3 6 ...
 $ C4       : int  4 3 2 5 3 1 2 2 4 2 ...
 $ C5       : int  4 4 5 5 2 3 3 4 5 1 ...
 $ E1       : int  3 1 2 5 2 2 4 3 5 2 ...
 $ E2       : int  3 1 4 3 2 1 3 6 3 2 ...
 $ E3       : int  3 6 4 4 5 6 4 4 NA 4 ...
 $ E4       : int  4 4 4 4 4 5 5 2 4 5 ...
 $ E5       : int  4 3 5 4 5 6 5 1 3 5 ...
 $ N1       : int  3 3 4 2 2 3 1 6 5 5 ...
 $ N2       : int  4 3 5 5 3 5 2 3 5 5 ...
 $ N3       : int  2 3 4 2 4 2 2 2 2 5 ...
 $ N4       : int  2 5 2 4 4 2 1 6 3 2 ...
 $ N5       : int  3 5 3 1 3 3 1 4 3 4 ...
 $ O1       : int  3 4 4 3 3 4 5 3 6 5 ...
 $ O2       : int  6 2 2 3 3 3 2 2 6 1 ...
 $ O3       : int  3 4 5 4 4 5 5 4 6 5 ...
 $ O4       : int  4 3 5 3 3 6 6 5 6 5 ...
 $ O5       : int  3 3 2 5 3 1 1 3 1 2 ...
 $ gender   : int  1 2 2 2 1 2 1 1 1 2 ...
 $ education: int  NA NA NA NA NA 3 NA 2 1 NA ...
 $ age      : int  16 18 17 17 17 21 18 19 19 17 ...
 $ Gender   : Factor w/ 2 levels "Male","Female": 1 2 2 2 1 2 1 1 1 2 ...
 $ Age      : num  16 18 17 17 17 21 18 19 19 17 ...
 $ Edu      : Factor w/ 5 levels "1","2","3","4",..: NA NA NA NA NA 3 NA 2 1 NA ...
 $ E        : num  3.8 5 4.2 3.6 4.8 5.6 4.2 2.4 3.25 4.8 ...
 $ A        : num  4 4.2 3.8 4.6 4 4.6 4.6 2.6 3.6 5.4 ...
 $ C        : num  2.8 4 4 3 4.4 5.6 4.4 3.4 4 5.6 ...
 $ N        : num  2.8 3.8 3.6 2.8 3.2 3 1.4 4.2 3.6 4.2 ...
 $ O        : num  3 4 4.8 3.2 3.6 5 5.4 4.2 5 5.2 ...
 - attr(*, ".internal.selfref")=<pointer: 0x000001fbac151770> 
p = ggplot(data.bfi[Age>=18 & Age<60 & !is.na(Edu)],
           aes(x=Age, y=E)) +
  geom_point(alpha=0.1) +
  geom_smooth(method="loess", color="firebrick", fill="orange") +
  labs(y="Extraversion")
p
`geom_smooth()` using formula = 'y ~ x'

p + facet_wrap(~ Gender)
`geom_smooth()` using formula = 'y ~ x'

p + facet_wrap(~ Gender + Edu)  # 并不理想
`geom_smooth()` using formula = 'y ~ x'

p + facet_wrap(~ Gender * Edu)  # 并不理想
`geom_smooth()` using formula = 'y ~ x'

p + facet_grid(~ Gender)  # . ~ 列分组变量(.点可以省略)
`geom_smooth()` using formula = 'y ~ x'

p + facet_grid(Gender ~ .)  # 行分组变量 ~ .(.点不能省略)
`geom_smooth()` using formula = 'y ~ x'

p + facet_grid(Gender ~ Edu)  # 行变量 ~ 列变量
`geom_smooth()` using formula = 'y ~ x'

p + facet_grid(Edu ~ Gender)  # 行变量 ~ 列变量
`geom_smooth()` using formula = 'y ~ x'

多图组合

【实践3】cowplot::plot_grid():多图组合

p1 = ggplot(data, aes(x=Temp.C)) +
  geom_histogram(bins=10, color="black", fill="grey") +
  labs(x="Temperature", y="Frequency")

p2 = ggplot(data, aes(x=Month, y=Temp.C)) +
  geom_boxplot() +
  labs(y="Temperature")

p12 = cowplot::plot_grid(p1, p2, nrow=1, labels="AUTO")
p12

p3 = ggplot(data, aes(x=Wind, y=Temp.C)) +
  geom_point() +
  geom_smooth(method="lm") +
  labs(y="Temperature")

p4 = ggplot(data, aes(x=Wind, y=Temp.C, color=Month)) +
  geom_point(show.legend=FALSE) +
  geom_smooth(method="lm", se=FALSE, show.legend=FALSE) +
  facet_wrap(~ Month, nrow=1) +
  labs(y="Temperature")

p1234 = cowplot::plot_grid(p1, p2, p3, p4, ncol=2, labels="AUTO")
`geom_smooth()` using formula = 'y ~ x'
`geom_smooth()` using formula = 'y ~ x'
p1234

图形文件保存

【实践4】ggsave():图形文件保存

ggsave():保存ggplot对象到文件

  • file:可以是.png.jpg.pdf等文件格式
    • PDF格式是矢量图,无限放大依然清晰
  • width:宽度(英寸inch)
  • height:高度(英寸inch)
  • dpi(dots per inch):每英寸像素点数
ggsave(p1234, file="Fig1.png", width=8, height=6, dpi=300)
# 分辨率:2400 * 1800(比例合适,分辨率高)

ggsave(p1234, file="Fig2.png", width=4, height=3, dpi=300)
# 分辨率:1200 * 900(比例不合适,虽然分辨率高)

ggsave(p1234, file="Fig3.png", width=8, height=6, dpi=100)
# 分辨率:800 * 600(比例合适,但分辨率低)

ggsave(p1234, file="Fig4.png", width=24, height=18, dpi=100)
# 分辨率:2400 * 1800(比例不合适,分辨率也低)

【作业10】多图组合练习

作业要求:

  • 使用cowplot::plot_grid()函数,把【作业9】完成的4张图拼合在一起,添加A~D标签,最后保存为合适尺寸的高清晰度图形文件(.png格式,清晰度dpi至少为300)
  • 使用R Markdown完成,对关键代码及结果要有注释说明

平台提交:

  • 关键代码截图及保存的PNG图形

返回课程主页

© 包寒吴霜

LS0tDQp0aXRsZTogIuOAilLor63oqIDjgIvnrKwxMeeroO+8muWkmuWbvue7hOWQiCINCnN1YnRpdGxlOiA8YSBocmVmPSJodHRwczovL3BzeWNoYnJ1Y2UuZ2l0aHViLmlvL1JDb3Vyc2UvIj7ov5Tlm57or77nqIvkuLvpobU8L2E+DQphdXRob3I6ICLmjojor77mlZnluIjvvJrljIXlr5LlkLTpnJwiDQojIGRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZGVwdGg6IDMNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgICBzbW9vdGhfc2Nyb2xsOiBmYWxzZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBhbmNob3Jfc2VjdGlvbnM6IHRydWUNCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzDQogICAgY3NzOiBSbWRDU1MuY3NzDQotLS0NCg0KYGBgez1odG1sfQ0KPHAgc3R5bGU9ImZvbnQtc2l6ZTogMTJweCI+54mI5p2D5aOw5piO77ya5pys5aWX6K++56iL5p2Q5paZ5byA5rqQ77yM5L2/55So5ZKM5YiG5Lqr5b+F6aG76YG15a6I44CM5Yib5L2c5YWx55So6K645Y+v5Y2P6K6uIENDIEJZLU5DLVNB44CN77yI5p2l5rqQ5byV55SoLemdnuWVhuS4mueUqOmAlOS9v+eUqC3ku6Xnm7jlkIzmlrnlvI/lhbHkuqvvvInjgII8aW1nIHNyYz0iaW1nL0NDLUJZLU5DLVNBLmpwZyIgd2lkdGg9IjEyMHB4IiBoZWlnaHQ9IjQycHgiIHN0eWxlPSJmbG9hdDogcmlnaHQiIC8+PC9wPg0KYGBgDQoNCmBgYHtyIENvbmZpZywgaW5jbHVkZT1GQUxTRX0NCm9wdGlvbnMoDQogIGtuaXRyLmthYmxlLk5BID0gIiIsDQogIGRpZ2l0cyA9IDQNCikNCmtuaXRyOjpvcHRzX2NodW5rJHNldCgNCiAgY29tbWVudCA9ICIiLA0KICBmaWcud2lkdGggPSA2LA0KICBmaWcuaGVpZ2h0ID0gNCwNCiAgZHBpID0gMzAwDQopDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgQ2hhcDEx77ya5aSa5Zu+57uE5ZCIDQoNCiMjIyMg5b6A5pyf6KaB54K55Zue6aG+DQoNCi0gW0NoYXAxMCBcIyDnu5jliLblj5jph4/liIbluINdKGh0dHBzOi8vcHN5Y2hicnVjZS5naXRodWIuaW8vUkNvdXJzZS9DaGFwMTAjJUU3JUJCJTk4JUU1JTg4JUI2JUU1JThGJTk4JUU5JTg3JThGJUU1JTg4JTg2JUU1JUI4JTgzKXsudXJpfe+8iOebtOaWueWbvu+8iQ0KLSBbQ2hhcDEwIFwjIOe7mOWItuWPmOmHj+Wkp+Wwj10oaHR0cHM6Ly9wc3ljaGJydWNlLmdpdGh1Yi5pby9SQ291cnNlL0NoYXAxMCMlRTclQkIlOTglRTUlODglQjYlRTUlOEYlOTglRTklODclOEYlRTUlQTQlQTclRTUlQjAlOEYpey51cml977yI5p+x5b2i5Zu+77yJDQotIFtDaGFwMTAgXCMg57uY5Yi25Y+Y6YeP5YWz57O7XShodHRwczovL3BzeWNoYnJ1Y2UuZ2l0aHViLmlvL1JDb3Vyc2UvQ2hhcDEwIyVFNyVCQiU5OCVFNSU4OCVCNiVFNSU4RiU5OCVFOSU4NyU4RiVFNSU4NSVCMyVFNyVCMyVCQil7LnVyaX3vvIjmlaPngrnlm77vvIkNCi0gW0NoYXAxMCBcIyDnu5jliLblj5jph4/otovlir9dKGh0dHBzOi8vcHN5Y2hicnVjZS5naXRodWIuaW8vUkNvdXJzZS9DaGFwMTAjJUU3JUJCJTk4JUU1JTg4JUI2JUU1JThGJTk4JUU5JTg3JThGJUU4JUI2JThCJUU1JThBJUJGKXsudXJpfe+8iOaKmOe6v+Wbvu+8iQ0KDQojIyMjIOacrOeroOimgeeCueebruW9lQ0KDQotIFvjgJDlrp7ot7Ux44CRZmFjZXRfd3JhcCgp77ya5ouG6Kej5YiG6Z2i5bCP5Zu+XSgj5a6e6Le1MWZhY2V0X3dyYXDmi4bop6PliIbpnaLlsI/lm74pDQotIFvjgJDlrp7ot7Uy44CRZmFjZXRfZ3JpZCgp77ya5Lik5Liq5YiG57G75Y+Y6YeP55qE5Lqk5Y+J57uE5ZCI5YiG6Z2iXSgj5a6e6Le1MmZhY2V0X2dyaWTkuKTkuKrliIbnsbvlj5jph4/nmoTkuqTlj4nnu4TlkIjliIbpnaIpDQotIFvjgJDlrp7ot7Uz44CRY293cGxvdDo6cGxvdF9ncmlkKCnvvJrlpJrlm77nu4TlkIhdKCPlrp7ot7UzY293cGxvdHBsb3RfZ3JpZOWkmuWbvue7hOWQiCnvvIjph43ngrnvvIkNCi0gW+OAkOWunui3tTTjgJFnZ3NhdmUoKe+8muWbvuW9ouaWh+S7tuS/neWtmF0oI+Wunui3tTRnZ3NhdmXlm77lvaLmlofku7bkv53lrZgp77yI6YeN54K577yJDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyMg5pys56ug5omA6ZyAUuWMhQ0KbGlicmFyeShicnVjZVIpDQojIGxpYnJhcnkoZ2dwbG90MikgICMg5Yqg6L29YnJ1Y2VS5pe25bey6buY6K6k5Yqg6L29Z2dwbG90Mg0KIyBsaWJyYXJ5KGNvd3Bsb3QpICAjIOWKoOi9vWJydWNlUuaXtuW3sum7mOiupOWKoOi9vWNvd3Bsb3QNCmBgYA0KDQojIOWIhumdouWwj+Wbvg0KDQojIyMjIOOAkOWunui3tTHjgJFmYWNldF93cmFwKCnvvJrmi4bop6PliIbpnaLlsI/lm74geyPlrp7ot7UxZmFjZXRfd3JhcOaLhuino+WIhumdouWwj+Wbvn0NCg0KYGBge3J9DQojIyDmlbDmja7lh4blpIcNCmRhdGEgPSBhaXJxdWFsaXR5DQpkYXRhJE1vbnRoID0gYXMuZmFjdG9yKGRhdGEkTW9udGgpDQpkYXRhJFRlbXAuQyA9IChkYXRhJFRlbXAgLSAzMikgLyAxLjggICMg5pGE5rCP5bqmID0gKOWNjuawj+W6piAtIDMyKSAvIDEuOA0Kc3RyKGRhdGEpDQoNCiMjIOWfuuehgOaVo+eCueWbvg0KZ2dwbG90KGRhdGEsIGFlcyh4PVdpbmQsIHk9VGVtcC5DKSkgKw0KICBnZW9tX3BvaW50KCkNCg0KZ2dwbG90KGRhdGEsIGFlcyh4PVdpbmQsIHk9VGVtcC5DLCBjb2xvcj1Nb250aCkpICsNCiAgZ2VvbV9wb2ludCgpDQoNCmdncGxvdChkYXRhLCBhZXMoeD1XaW5kLCB5PVRlbXAuQywgY29sb3I9TW9udGgpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGZhY2V0X3dyYXAofiBNb250aCkNCg0KZ2dwbG90KGRhdGEsIGFlcyh4PVdpbmQsIHk9VGVtcC5DLCBjb2xvcj1Nb250aCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZmFjZXRfd3JhcCh+IE1vbnRoLCBuY29sPTUpDQoNCmdncGxvdChkYXRhLCBhZXMoeD1XaW5kLCB5PVRlbXAuQywgY29sb3I9TW9udGgpKSArDQogIGdlb21fcG9pbnQoc2hvdy5sZWdlbmQ9RkFMU0UpICsNCiAgZmFjZXRfd3JhcCh+IE1vbnRoLCBucm93PTEpDQoNCmdncGxvdChkYXRhLCBhZXMoeD1XaW5kLCB5PVRlbXAuQywgY29sb3I9TW9udGgpKSArDQogIGdlb21fcG9pbnQoc2hvdy5sZWdlbmQ9RkFMU0UpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsIHNlPUZBTFNFLCBzaG93LmxlZ2VuZD1GQUxTRSkgKw0KICBmYWNldF93cmFwKH4gTW9udGgsIG5yb3c9MSkNCg0KIyMg5L+u5pS55Zug5a2Q5qCH562+DQpkYXRhJE1vbnRocyA9IGZhY3RvcigNCiAgZGF0YSRNb250aCwNCiAgbGV2ZWxzID0gNTo5LA0KICBsYWJlbHMgPSBjKCJNYXkiLCAiSnVuZSIsICJKdWx5IiwgIkF1Z3VzdCIsICJTZXB0ZW1iZXIiKQ0KKQ0KZ2dwbG90KGRhdGEsIGFlcyh4PVdpbmQsIHk9VGVtcC5DLCBjb2xvcj1Nb250aHMpKSArDQogIGdlb21fcG9pbnQoc2hvdy5sZWdlbmQ9RkFMU0UpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsIHNob3cubGVnZW5kPUZBTFNFKSArDQogIGZhY2V0X3dyYXAofiBNb250aHMsIG5yb3c9MSkNCg0KZ2dwbG90KGRhdGEsIGFlcyh4PVdpbmQsIHk9VGVtcC5DLCBjb2xvcj1Nb250aHMpKSArDQogIGdlb21fcG9pbnQoc2hvdy5sZWdlbmQ9RkFMU0UpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsIHNob3cubGVnZW5kPUZBTFNFKSArDQogIGZhY2V0X3dyYXAofiBNb250aHMsIG5yb3c9MSwgc2NhbGVzPSJmcmVlIikNCmBgYA0KDQojIyMjIOOAkOWunui3tTLjgJFmYWNldF9ncmlkKCnvvJrkuKTkuKrliIbnsbvlj5jph4/nmoTkuqTlj4nnu4TlkIjliIbpnaIgeyPlrp7ot7UyZmFjZXRfZ3JpZOS4pOS4quWIhuexu+WPmOmHj+eahOS6pOWPiee7hOWQiOWIhumdon0NCg0KLSBgZmFjZXRfd3JhcCgpYO+8mue6v+aAp+aOkuWIl++8iOWNt+mlvPCfjK/vvIkNCiAgLSBgfiDliIbnu4Tlj5jph49gDQotIGBmYWNldF9ncmlkKClg77ya5Lik5Zug57Sg5Lqk5Y+J57uE5ZCI5o6S5YiX77yI5Y2O5aSr6aW88J+nh++8iQ0KICAtIGDooYzliIbnu4Tlj5jph48gfiDliJfliIbnu4Tlj5jph49gDQoNCiFbXShpbWFnZXMvY2xpcGJvYXJkLTE1MDUzNzIzMTUucG5nKQ0KDQpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0NCiMjIOS4pOS4quWIhuexu+WPmOmHj+eahOaDheWGtQ0KIyMg5pWw5o2u5Y+Y6YeP6K6h566X5aSN5Lmg77yIQ2hhcCAwNSAmIDA277yJDQpkYXRhLmJmaSA9IGFzLmRhdGEudGFibGUocHN5Y2g6OmJmaSkNCmRhdGEuYmZpWywgbGV0KA0KICBHZW5kZXIgPSBmYWN0b3IoZ2VuZGVyLCBsZXZlbHM9MToyLCBsYWJlbHM9YygiTWFsZSIsICJGZW1hbGUiKSksDQogIEFnZSA9IGFzLm51bWVyaWMoYWdlKSwNCiAgRWR1ID0gYXMuZmFjdG9yKGVkdWNhdGlvbiksDQogIEUgPSBNRUFOKGRhdGEuYmZpLCAiRSIsIDE6NSwgcmV2PWMoMSwyKSwgcmFuZ2U9MTo2KSwNCiAgQSA9IE1FQU4oZGF0YS5iZmksICJBIiwgMTo1LCByZXY9MSwgcmFuZ2U9MTo2KSwNCiAgQyA9IE1FQU4oZGF0YS5iZmksICJDIiwgMTo1LCByZXY9Yyg0LDUpLCByYW5nZT0xOjYpLA0KICBOID0gTUVBTihkYXRhLmJmaSwgIk4iLCAxOjUsIHJldj1OVUxMLCByYW5nZT0xOjYpLA0KICBPID0gTUVBTihkYXRhLmJmaSwgIk8iLCAxOjUsIHJldj1jKDIsNSksIHJhbmdlPTE6NikNCildDQpzdHIoZGF0YS5iZmkpDQoNCnAgPSBnZ3Bsb3QoZGF0YS5iZmlbQWdlPj0xOCAmIEFnZTw2MCAmICFpcy5uYShFZHUpXSwNCiAgICAgICAgICAgYWVzKHg9QWdlLCB5PUUpKSArDQogIGdlb21fcG9pbnQoYWxwaGE9MC4xKSArDQogIGdlb21fc21vb3RoKG1ldGhvZD0ibG9lc3MiLCBjb2xvcj0iZmlyZWJyaWNrIiwgZmlsbD0ib3JhbmdlIikgKw0KICBsYWJzKHk9IkV4dHJhdmVyc2lvbiIpDQpwDQoNCnAgKyBmYWNldF93cmFwKH4gR2VuZGVyKQ0KcCArIGZhY2V0X3dyYXAofiBHZW5kZXIgKyBFZHUpICAjIOW5tuS4jeeQhuaDsw0KcCArIGZhY2V0X3dyYXAofiBHZW5kZXIgKiBFZHUpICAjIOW5tuS4jeeQhuaDsw0KcCArIGZhY2V0X2dyaWQofiBHZW5kZXIpICAjIC4gfiDliJfliIbnu4Tlj5jph4/vvIgu54K55Y+v5Lul55yB55Wl77yJDQpwICsgZmFjZXRfZ3JpZChHZW5kZXIgfiAuKSAgIyDooYzliIbnu4Tlj5jph48gfiAu77yILueCueS4jeiDveecgeeVpe+8iQ0KcCArIGZhY2V0X2dyaWQoR2VuZGVyIH4gRWR1KSAgIyDooYzlj5jph48gfiDliJflj5jph48NCmBgYA0KDQpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9MTJ9DQpwICsgZmFjZXRfZ3JpZChFZHUgfiBHZW5kZXIpICAjIOihjOWPmOmHjyB+IOWIl+WPmOmHjw0KYGBgDQoNCiMg5aSa5Zu+57uE5ZCIDQoNCiMjIyMg44CQ5a6e6Le1M+OAkWNvd3Bsb3Q6OnBsb3RfZ3JpZCgp77ya5aSa5Zu+57uE5ZCIIHsj5a6e6Le1M2Nvd3Bsb3RwbG90X2dyaWTlpJrlm77nu4TlkIh9DQoNCmBgYHtyfQ0KcDEgPSBnZ3Bsb3QoZGF0YSwgYWVzKHg9VGVtcC5DKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zPTEwLCBjb2xvcj0iYmxhY2siLCBmaWxsPSJncmV5IikgKw0KICBsYWJzKHg9IlRlbXBlcmF0dXJlIiwgeT0iRnJlcXVlbmN5IikNCg0KcDIgPSBnZ3Bsb3QoZGF0YSwgYWVzKHg9TW9udGgsIHk9VGVtcC5DKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGxhYnMoeT0iVGVtcGVyYXR1cmUiKQ0KDQpwMTIgPSBjb3dwbG90OjpwbG90X2dyaWQocDEsIHAyLCBucm93PTEsIGxhYmVscz0iQVVUTyIpDQpwMTINCmBgYA0KDQpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0NCnAzID0gZ2dwbG90KGRhdGEsIGFlcyh4PVdpbmQsIHk9VGVtcC5DKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIikgKw0KICBsYWJzKHk9IlRlbXBlcmF0dXJlIikNCg0KcDQgPSBnZ3Bsb3QoZGF0YSwgYWVzKHg9V2luZCwgeT1UZW1wLkMsIGNvbG9yPU1vbnRoKSkgKw0KICBnZW9tX3BvaW50KHNob3cubGVnZW5kPUZBTFNFKSArDQogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLCBzZT1GQUxTRSwgc2hvdy5sZWdlbmQ9RkFMU0UpICsNCiAgZmFjZXRfd3JhcCh+IE1vbnRoLCBucm93PTEpICsNCiAgbGFicyh5PSJUZW1wZXJhdHVyZSIpDQoNCnAxMjM0ID0gY293cGxvdDo6cGxvdF9ncmlkKHAxLCBwMiwgcDMsIHA0LCBuY29sPTIsIGxhYmVscz0iQVVUTyIpDQpwMTIzNA0KYGBgDQoNCiMg5Zu+5b2i5paH5Lu25L+d5a2YDQoNCiMjIyMg44CQ5a6e6Le1NOOAkWdnc2F2ZSgp77ya5Zu+5b2i5paH5Lu25L+d5a2YIHsj5a6e6Le1NGdnc2F2ZeWbvuW9ouaWh+S7tuS/neWtmH0NCg0KYGdnc2F2ZSgpYO+8muS/neWtmGBnZ3Bsb3Rg5a+56LGh5Yiw5paH5Lu2DQoNCi0gYGZpbGVg77ya5Y+v5Lul5pivYC5wbmdg44CBYC5qcGdg44CBYC5wZGZg562J5paH5Lu25qC85byPDQogIC0gUERG5qC85byP5piv55+i6YeP5Zu+77yM5peg6ZmQ5pS+5aSn5L6d54S25riF5pmwDQotIGB3aWR0aGDvvJrlrr3luqbvvIjoi7Hlr7hpbmNo77yJDQotIGBoZWlnaHRg77ya6auY5bqm77yI6Iux5a+4aW5jaO+8iQ0KLSBgZHBpYO+8iGRvdHMgcGVyIGluY2jvvInvvJrmr4/oi7Hlr7jlg4/ntKDngrnmlbANCg0KYGBge3J9DQpnZ3NhdmUocDEyMzQsIGZpbGU9IkZpZzEucG5nIiwgd2lkdGg9OCwgaGVpZ2h0PTYsIGRwaT0zMDApDQojIOWIhui+qOeOh++8mjI0MDAgKiAxODAw77yI5q+U5L6L5ZCI6YCC77yM5YiG6L6o546H6auY77yJDQpgYGANCg0KIVtdKGltZy9GaWcxLnBuZykNCg0KYGBge3J9DQpnZ3NhdmUocDEyMzQsIGZpbGU9IkZpZzIucG5nIiwgd2lkdGg9NCwgaGVpZ2h0PTMsIGRwaT0zMDApDQojIOWIhui+qOeOh++8mjEyMDAgKiA5MDDvvIjmr5TkvovkuI3lkIjpgILvvIzomb3nhLbliIbovqjnjofpq5jvvIkNCmBgYA0KDQohW10oaW1nL0ZpZzIucG5nKQ0KDQpgYGB7cn0NCmdnc2F2ZShwMTIzNCwgZmlsZT0iRmlnMy5wbmciLCB3aWR0aD04LCBoZWlnaHQ9NiwgZHBpPTEwMCkNCiMg5YiG6L6o546H77yaODAwICogNjAw77yI5q+U5L6L5ZCI6YCC77yM5L2G5YiG6L6o546H5L2O77yJDQpgYGANCg0KIVtdKGltZy9GaWczLnBuZykNCg0KYGBge3J9DQpnZ3NhdmUocDEyMzQsIGZpbGU9IkZpZzQucG5nIiwgd2lkdGg9MjQsIGhlaWdodD0xOCwgZHBpPTEwMCkNCiMg5YiG6L6o546H77yaMjQwMCAqIDE4MDDvvIjmr5TkvovkuI3lkIjpgILvvIzliIbovqjnjofkuZ/kvY7vvIkNCmBgYA0KDQohW10oaW1nL0ZpZzQucG5nKQ0KDQpgYGB7ciwgaW5jbHVkZT1GQUxTRX0NCnVubGluayhjKCJGaWcxLnBuZyIsICJGaWcyLnBuZyIsICJGaWczLnBuZyIsICJGaWc0LnBuZyIpKQ0KYGBgDQoNCiMg44CQ5L2c5LiaMTDjgJHlpJrlm77nu4TlkIjnu4PkuaANCg0K5L2c5Lia6KaB5rGC77yaDQoNCi0g5L2/55SoYGNvd3Bsb3Q6OnBsb3RfZ3JpZCgpYOWHveaVsO+8jOaKilvjgJDkvZzkuJo544CRXShodHRwczovL3BzeWNoYnJ1Y2UuZ2l0aHViLmlvL1JDb3Vyc2UvQ2hhcDEwIyVFNCVCRCU5QyVFNCVCOCU5QTklRTUlOUYlQkElRTclQTElODAlRTclQkIlOTglRTUlOUIlQkUlRTclQkIlODMlRTQlQjklQTApey51cml95a6M5oiQ55qENOW8oOWbvuaLvOWQiOWcqOS4gOi1t++8jOa3u+WKoEFcfkTmoIfnrb7vvIzmnIDlkI7kv53lrZjkuLrlkIjpgILlsLrlr7jnmoTpq5jmuIXmmbDluqblm77lvaLmlofku7bvvIhgLnBuZ2DmoLzlvI/vvIzmuIXmmbDluqZgZHBpYOiHs+WwkeS4ujMwMO+8iQ0KLSDkvb/nlKhSIE1hcmtkb3du5a6M5oiQ77yM5a+55YWz6ZSu5Luj56CB5Y+K57uT5p6c6KaB5pyJ5rOo6YeK6K+05piODQoNCuW5s+WPsOaPkOS6pO+8mg0KDQotIOWFs+mUruS7o+eggeaIquWbvuWPiuS/neWtmOeahFBOR+WbvuW9og0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KW+i/lOWbnuivvueoi+S4u+mhtV0oaHR0cHM6Ly9wc3ljaGJydWNlLmdpdGh1Yi5pby9SQ291cnNlLykNCg0Kwqkg5YyF5a+S5ZC06ZycDQo=