Giới thiệu
Một trong những động cơ thúc đẩy các bạn làm quen và chuyển sang dùng R, chính là vẻ đẹp quyến rũ của những biểu đồ thống kê mà R cung cấp. Khả năng đồ họa thống kê của R là không có giới hạn cả về mặt mỹ thuật và sáng tạo, nhờ vào nền tảng ngữ pháp đồ thị ggplot2 và rất nhiều packages khác phát triển dựa vào ggplot2.
Tuy nhiên, có một số việc mà ggplot2 cho đến nay vẫn chưa làm được, đó là
Trình bày biểu đồ tần suất và density curve của nhiều phân nhóm trên cùng một panel mà không chồng lắp chúng lên nhau
Áp dụng scale_fill_gradient trên density curve
Nếu việc thứ 2 chỉ có ý nghĩa mỹ thuật, thì việc thứ 1 có nhiều ý nghĩa khác về mặt ứng dụng, vì hiện nay cách duy nhất để so sánh phân phối giữa nhiều phân nhóm trong ggplot là sử dụng facet_wrap() hay facet_grid(); hoặc bạn phải dùng Box-plot, scatter plot.
Tháng 10 vừa qua, một package mới ra đời là ggridges đã giải quyết 2 vấn đề nêu trên một cách hoàn hảo. Sau đây Nhi xin được so sánh giữa những gì ggridges làm được so với ggplot2 làm trước kia:
Trước hết ta lấy thí dụ: Package birthwt : bộ số liệu về ảnh hưởng của sức khỏe người mẹ lên cân nặng của trẻ sơ sinh
library(viridis)
## Loading required package: viridisLite
library(ggridges)
## Loading required package: ggplot2
library(tidyverse)
## Loading tidyverse: tibble
## Loading tidyverse: tidyr
## Loading tidyverse: readr
## Loading tidyverse: purrr
## Loading tidyverse: dplyr
## Conflicts with tidy packages ----------------------------------------------
## filter(): dplyr, stats
## lag(): dplyr, stats
my_theme <- function(base_size =10, base_family = "sans"){
theme_bw(base_size = base_size, base_family = base_family) +
theme(
panel.grid.major = element_line(color = "gray"),
panel.grid.minor = element_blank(),
panel.background = element_rect(fill = "#f7faff"),
strip.background = element_rect(fill = "#001659", color = NA, size =0.5),
strip.text = element_text(face = "bold", size = 10, color = "white"),
legend.position = "bottom",
legend.justification = "center",
legend.background = element_blank(),
legend.margin = margin(0.5,0.5,0.5,0.5)
)
}
library(tidyverse)
df=read.csv("https://raw.github.com/vincentarelbundock/Rdatasets/master/csv/MASS/birthwt.csv")
df$smoke=df$smoke%>%recode_factor(.,`1` = "Smoke", `0` = "No_smoke")
df$ht=df$ht%>%recode_factor(.,`1` = "Hypertensive", `0` = "Normal")
df$race=df$race%>%recode_factor(.,`1` = "White", `2` = "Black", `3` = "Other")
Sử dụng histogram thay thế cho Box-plot:
# Pair 1
p1=ggplot(df, aes(x = bwt, y = race, fill= race)) +
geom_density_ridges(stat = "binline", scale = 1, bins=30,draw_baseline = FALSE,alpha=0.6) +
labs(x="Birthweight", y = "Race") + scale_fill_discrete(name = "Race") +
coord_flip() +
my_theme(8) +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+ggtitle("Histograms")
p2=ggplot(df, aes(y = bwt, x = race, fill= race)) +
geom_boxplot(alpha=0.8) +
labs(y="Birthweight", x = "Race")+
my_theme(8) +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+ggtitle("Tukey's Boxplot")
gridExtra::grid.arrange(p1,p2,ncol=2)

Hình này so sánh giữa 2 đồ thị dựng bằng ggridges (Trái) và ggplot2 cổ điển (Phải)
Trước hết, ggridges tạo ra một dạn biểu đồ mật độ phân phối mới, lai giữa histogram và density curve. Dạng này rất hữu dụng khi bạn đang khảo sát phân bố của 1 count data variable. Cả boxplot và density curve đều không hoàn toàn chính xác cho discrete và count data, vì thang đo của biến số là không liên tục.
Hơn thế nữa, ggridges cho phép các bạn biểu diễn 3 histogram density curve một cách tách biệt cho 3 phân nhóm chứ không chồng lắp chúng lên nhau, và thú vị hơn nữa, đó là nó cho phép áp dụng coord_flip() để xoay trục X/Y, cho phép trình bày density curve theo chiều dọc (trục Y).
Như vậy, nếu bạn đang có trong tay một countdata variable và 1 factor nhiều level, hình bên trái vừa đẹp hơn, vừa chính xác hơn so với boxplot.
Dĩ nhiên bạn cũng có thể dựng hình theo chiều ngang như thế này:
ggplot(df, aes(x = bwt, y = race, fill= race)) +
geom_density_ridges2(stat = "binline", draw_baseline = FALSE,alpha=0.6,scale = 0.9) +
geom_text(stat = "bin",
aes(y = group + 0.9*(..count../max(..count..)),
label = ifelse(..count..>0, ..count.., "")),size = 3, color = "black",vjust=1.5)+
labs(x="Birthweight", y = "Race")+
scale_x_continuous(expand = c(0.01,0))+
scale_y_discrete(expand = c(0.01, 0))+
my_theme(10) +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))
## `stat_binline()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Sử dụng density plot thay cho Boxplot
Trong trường hợp bạn có 1 biến liên tục, lúc này density curve là giải pháp rất hợp lý để biểu diễn phân phối của biến Y; Tuy nhiên nếu chỉ dùng ggplot2, bạn không thể so sánh đặc tính phân phối của nhiều phân nhóm mà không dùng facet plot.
ggridges cho phép bạn ghép 3 density curve theo chiều dọc hay chiều ngang trên cùng 1 panel;
Từ khi phát hiện ggridges, Nhi bắt đầu thay thế Boxplot bằng density plot theo chiều ngang hay dọc thế này:
So với Boxplot truyền thống, hình vẽ 3 densityplot bên trái mang lại nhiều thông tin hơn hẳn. Boxplot chỉ cho ta biết thông tin tóm tắt về tứ phân vị,trung vị trong khi density plot cho ta hình ảnh về phân phối một cách trung thực và chi tiết đến từng bách phân vị. Việc thể hiện outlier trên Boxplot là có ích, nhưng - đôi khi những giá trị này không phải là outliers, mà chỉ là phần cực tiểu hay cực đại của mộ quy luật phân phối lệch trái hay phải, thí dụ Gamma…
q1=ggplot(df, aes(x = bwt, y = smoke, fill= smoke)) +
geom_density_ridges(scale = 1.5,alpha=0.6) +
labs(x="Birthweight", y = "Smoking status") + scale_fill_discrete(name = "Smoking") +
my_theme(8) +
geom_rug(aes(col=smoke),show.legend = F)+coord_flip()+
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+facet_wrap(~race)+ggtitle("Density curves")
q2=ggplot(df, aes(y = bwt, x = smoke, fill= smoke)) +
geom_boxplot(alpha=0.8) +
labs(y="Birthweight", x = "Smoking status") +
my_theme(8) +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+facet_wrap(~race)+ggtitle("Tukey's Boxplot")
gridExtra::grid.arrange(q1,q2,ncol=2)
## Picking joint bandwidth of 247
## Picking joint bandwidth of 293
## Picking joint bandwidth of 327

Tô màu theo gradient cho densityplot
Không chỉ cho phép tách biệt và đảo chiều những density plot, ggridges 2 còn cho phép áp dụng gradient màu cho các densityplot này.
Trước kia, ggplot2 chỉ cho phép dùng gradient màu cho geom_point, scale_fill_gradient hay scale_fill_continuous không thể dùng cho những hình có diện tích, như boxplot, violin plot hay density plot. Bây giờ thì bạn có thể tô màu gradient cho cả density plot : Hình vẽ đẹp hơn và gây ấn tượng mạnh hơn.
s1=ggplot(df, aes(x = bwt, y = race,fill = ..x..))+
geom_density_ridges_gradient(scale = 1,alpha=0.5,gradient_lwd = 0.5,show.legend = F) +
labs(x="Birthweight", y = "Race") +
my_theme(8)+
scale_x_continuous(expand = c(0.01, 0))+
scale_y_discrete(expand = c(0.01, 0))+
my_theme(8)+
scale_fill_viridis(option="C")+
coord_flip()+ggtitle("Gradient density plots")
s2=ggplot(df, aes(y=bwt,x=race,fill=bwt))+
geom_point(shape=21,aes(col=bwt,size=bwt),alpha=0.6,show.legend = F) +
labs(y="Birthweight", x = "Race") +
my_theme(8)+
scale_fill_viridis(option="C")+
scale_colour_viridis(option="C")+ggtitle("geom_point")
gridExtra::grid.arrange(s1,s2,ncol=2)
## Picking joint bandwidth of 261

Kết luận
Bạn có thể tải package ggridges từ CRAN: https://cran.r-project.org/web/packages/ggridges/index.html Và dùng thử. Nhi tin chắc sau khi đọc bài này và áp dụng ggridges, các bạn sẽ nhanh chóng chán Boxplot và thay thế chúng bằng densityplot khi làm thống kê mô tả/ thăm dò dữ liệu.
Những dạng biểu đồ này cũng cực kì hữu ích khi bạn làm bootstrap và phân tích Bayes.
Tạm biệt các bạn
LS0tDQp0aXRsZTogIlBhY2thZ2UgZ2dyaWRnZXMiDQpzdWJ0aXRsZTogIkdp4bqjaSBwaMOhcCBt4bu5IHRodeG6rXQgbeG7m2kiDQphdXRob3I6ICJMw6ogTmfhu41jIEto4bqjIE5oaSINCmRhdGU6ICIxMCBUaMOhbmcgMTEgMjAxNyINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6ICJkZWZhdWx0Ig0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCiFbXShnZ3JpZGVzUGFja2FnZS5wbmcpDQoNCiMgR2nhu5tpIHRoaeG7h3UNCg0KTeG7mXQgdHJvbmcgbmjhu69uZyDEkeG7mW5nIGPGoSB0aMO6YyDEkeG6qXkgY8OhYyBi4bqhbiBsw6BtIHF1ZW4gdsOgIGNodXnhu4NuIHNhbmcgZMO5bmcgUiwgY2jDrW5oIGzDoCB24bq7IMSR4bq5cCBxdXnhur9uIHLFqSBj4bunYSBuaOG7r25nIGJp4buDdSDEkeG7kyB0aOG7kW5nIGvDqiBtw6AgUiBjdW5nIGPhuqVwLiBLaOG6oyBuxINuZyDEkeG7kyBo4buNYSB0aOG7kW5nIGvDqiBj4bunYSBSIGzDoCBraMO0bmcgY8OzIGdp4bubaSBo4bqhbiBj4bqjIHbhu4EgbeG6t3QgbeG7uSB0aHXhuq10IHbDoCBzw6FuZyB04bqhbywgbmjhu50gdsOgbyBu4buBbiB04bqjbmcgbmfhu68gcGjDoXAgxJHhu5MgdGjhu4sgZ2dwbG90MiB2w6AgcuG6pXQgbmhp4buBdSBwYWNrYWdlcyBraMOhYyBwaMOhdCB0cmnhu4NuIGThu7FhIHbDoG8gZ2dwbG90Mi4NCg0KVHV5IG5oacOqbiwgY8OzIG3hu5l0IHPhu5Egdmnhu4djIG3DoCBnZ3Bsb3QyIGNobyDEkeG6v24gbmF5IHbhuqtuIGNoxrBhIGzDoG0gxJHGsOG7o2MsIMSRw7MgbMOgIA0KDQoxKSBUcsOsbmggYsOgeSBiaeG7g3UgxJHhu5MgdOG6p24gc3XhuqV0IHbDoCBkZW5zaXR5IGN1cnZlIGPhu6dhIG5oaeG7gXUgcGjDom4gbmjDs20gdHLDqm4gY8O5bmcgbeG7mXQgcGFuZWwgbcOgIGtow7RuZyBjaOG7k25nIGzhuq9wIGNow7puZyBsw6puIG5oYXUgDQoNCg0KMikgw4FwIGThu6VuZyBzY2FsZV9maWxsX2dyYWRpZW50IHRyw6puIGRlbnNpdHkgY3VydmUNCg0KTuG6v3Ugdmnhu4djIHRo4bupIDIgY2jhu4kgY8OzIMO9IG5naMSpYSBt4bu5IHRodeG6rXQsIHRow6wgdmnhu4djIHRo4bupIDEgY8OzIG5oaeG7gXUgw70gbmdoxKlhIGtow6FjIHbhu4EgbeG6t3Qg4bupbmcgZOG7pW5nLCB2w6wgaGnhu4duIG5heSBjw6FjaCBkdXkgbmjhuqV0IMSR4buDIHNvIHPDoW5oIHBow6JuIHBo4buRaSBnaeG7r2Egbmhp4buBdSBwaMOibiBuaMOzbSB0cm9uZyBnZ3Bsb3QgbMOgIHPhu60gZOG7pW5nIGZhY2V0X3dyYXAoKSBoYXkgZmFjZXRfZ3JpZCgpOyBob+G6t2MgYuG6oW4gcGjhuqNpIGTDuW5nIEJveC1wbG90LCBzY2F0dGVyIHBsb3QuIA0KDQpUaMOhbmcgMTAgduG7q2EgcXVhLCBt4buZdCBwYWNrYWdlIG3hu5tpIHJhIMSR4budaSBsw6AgZ2dyaWRnZXMgxJHDoyBnaeG6o2kgcXV54bq/dCAyIHbhuqVuIMSR4buBIG7DqnUgdHLDqm4gbeG7mXQgY8OhY2ggaG/DoG4gaOG6o28uIFNhdSDEkcOieSBOaGkgeGluIMSRxrDhu6NjIHNvIHPDoW5oIGdp4buvYSBuaOG7r25nIGfDrCBnZ3JpZGdlcyBsw6BtIMSRxrDhu6NjIHNvIHbhu5tpIGdncGxvdDIgbMOgbSB0csaw4bubYyBraWE6DQoNClRyxrDhu5tjIGjhur90IHRhIGzhuqV5IHRow60gZOG7pTogUGFja2FnZSBiaXJ0aHd0IDogYuG7mSBz4buRIGxp4buHdSB24buBIOG6o25oIGjGsOG7n25nIGPhu6dhIHPhu6ljIGto4buPZSBuZ8aw4budaSBt4bq5IGzDqm4gY8OibiBu4bq3bmcgY+G7p2EgdHLhursgc8ahIHNpbmggDQoNCmBgYHtyfQ0KbGlicmFyeSh2aXJpZGlzKQ0KbGlicmFyeShnZ3JpZGdlcykNCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQpteV90aGVtZSA8LSBmdW5jdGlvbihiYXNlX3NpemUgPTEwLCBiYXNlX2ZhbWlseSA9ICJzYW5zIil7DQogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IGJhc2Vfc2l6ZSwgYmFzZV9mYW1pbHkgPSBiYXNlX2ZhbWlseSkgKw0KICAgIHRoZW1lKA0KICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJncmF5IiksDQogICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIiNmN2ZhZmYiKSwNCiAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICIjMDAxNjU5IiwgY29sb3IgPSBOQSwgc2l6ZSA9MC41KSwNCiAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDEwLCBjb2xvciA9ICJ3aGl0ZSIpLA0KICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsDQogICAgICBsZWdlbmQuanVzdGlmaWNhdGlvbiA9ICJjZW50ZXIiLA0KICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICBsZWdlbmQubWFyZ2luID0gbWFyZ2luKDAuNSwwLjUsMC41LDAuNSkNCiAgICApDQp9DQoNCg0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCmRmPXJlYWQuY3N2KCJodHRwczovL3Jhdy5naXRodWIuY29tL3ZpbmNlbnRhcmVsYnVuZG9jay9SZGF0YXNldHMvbWFzdGVyL2Nzdi9NQVNTL2JpcnRod3QuY3N2IikNCg0KZGYkc21va2U9ZGYkc21va2UlPiVyZWNvZGVfZmFjdG9yKC4sYDFgID0gIlNtb2tlIiwgYDBgID0gIk5vX3Ntb2tlIikNCmRmJGh0PWRmJGh0JT4lcmVjb2RlX2ZhY3RvciguLGAxYCA9ICJIeXBlcnRlbnNpdmUiLCBgMGAgPSAiTm9ybWFsIikNCmRmJHJhY2U9ZGYkcmFjZSU+JXJlY29kZV9mYWN0b3IoLixgMWAgPSAiV2hpdGUiLCBgMmAgPSAiQmxhY2siLCBgM2AgPSAiT3RoZXIiKQ0KDQpgYGANCg0KIyBT4butIGThu6VuZyBoaXN0b2dyYW0gdGhheSB0aOG6vyBjaG8gQm94LXBsb3Q6DQoNCmBgYHtyfQ0KIyBQYWlyIDENCiAgcDE9Z2dwbG90KGRmLCBhZXMoeCA9IGJ3dCwgeSA9IHJhY2UsIGZpbGw9IHJhY2UpKSArIA0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzKHN0YXQgPSAiYmlubGluZSIsIHNjYWxlID0gMSwgYmlucz0zMCxkcmF3X2Jhc2VsaW5lID0gRkFMU0UsYWxwaGE9MC42KSArDQogIGxhYnMoeD0iQmlydGh3ZWlnaHQiLCB5ID0gIlJhY2UiKSArIHNjYWxlX2ZpbGxfZGlzY3JldGUobmFtZSA9ICJSYWNlIikgKw0KICBjb29yZF9mbGlwKCkgKw0KICBteV90aGVtZSg4KSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpK2dndGl0bGUoIkhpc3RvZ3JhbXMiKQ0KICANCiAgDQogIHAyPWdncGxvdChkZiwgYWVzKHkgPSBid3QsIHggPSByYWNlLCBmaWxsPSByYWNlKSkgKyANCiAgICBnZW9tX2JveHBsb3QoYWxwaGE9MC44KSArDQogICAgbGFicyh5PSJCaXJ0aHdlaWdodCIsIHggPSAiUmFjZSIpKw0KICAgIG15X3RoZW1lKDgpICsNCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKStnZ3RpdGxlKCJUdWtleSdzIEJveHBsb3QiKQ0KICANCiAgZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UocDEscDIsbmNvbD0yKQ0KICANCmBgYA0KDQpIw6xuaCBuw6B5IHNvIHPDoW5oIGdp4buvYSAyIMSR4buTIHRo4buLIGThu7FuZyBi4bqxbmcgZ2dyaWRnZXMgKFRyw6FpKSB2w6AgZ2dwbG90MiBj4buVIMSRaeG7g24gKFBo4bqjaSkNCg0KVHLGsOG7m2MgaOG6v3QsIGdncmlkZ2VzIHThuqFvIHJhIG3hu5l0IGThuqFuIGJp4buDdSDEkeG7kyBt4bqtdCDEkeG7mSBwaMOibiBwaOG7kWkgbeG7m2ksIGxhaSBnaeG7r2EgaGlzdG9ncmFtIHbDoCBkZW5zaXR5IGN1cnZlLiBE4bqhbmcgbsOgeSBy4bqldCBo4buvdSBk4bulbmcga2hpIGLhuqFuIMSRYW5nIGto4bqjbyBzw6F0IHBow6JuIGLhu5EgY+G7p2EgMSBjb3VudCBkYXRhIHZhcmlhYmxlLiBD4bqjIGJveHBsb3QgdsOgIGRlbnNpdHkgY3VydmUgxJHhu4F1IGtow7RuZyBob8OgbiB0b8OgbiBjaMOtbmggeMOhYyBjaG8gZGlzY3JldGUgdsOgIGNvdW50IGRhdGEsIHbDrCB0aGFuZyDEkW8gY+G7p2EgYmnhur9uIHPhu5EgbMOgIGtow7RuZyBsacOqbiB04bulYy4NCg0KSMahbiB0aOG6vyBu4buvYSwgZ2dyaWRnZXMgY2hvIHBow6lwIGPDoWMgYuG6oW4gYmnhu4N1IGRp4buFbiAzIGhpc3RvZ3JhbSBkZW5zaXR5IGN1cnZlIG3hu5l0IGPDoWNoIHTDoWNoIGJp4buHdCBjaG8gMyBwaMOibiBuaMOzbSBjaOG7qSBraMO0bmcgY2jhu5NuZyBs4bqvcCBjaMO6bmcgbMOqbiBuaGF1LCB2w6AgdGjDuiB24buLIGjGoW4gbuG7r2EsIMSRw7MgbMOgIG7DsyBjaG8gcGjDqXAgw6FwIGThu6VuZyBjb29yZF9mbGlwKCkgxJHhu4MgeG9heSB0cuG7pWMgWC9ZLCBjaG8gcGjDqXAgdHLDrG5oIGLDoHkgZGVuc2l0eSBjdXJ2ZSB0aGVvIGNoaeG7gXUgZOG7jWMgKHRy4bulYyBZKS4NCg0KTmjGsCB24bqteSwgbuG6v3UgYuG6oW4gxJFhbmcgY8OzIHRyb25nIHRheSBt4buZdCBjb3VudGRhdGEgdmFyaWFibGUgdsOgIDEgZmFjdG9yIG5oaeG7gXUgbGV2ZWwsIGjDrG5oIGLDqm4gdHLDoWkgduG7q2EgxJHhurlwIGjGoW4sIHbhu6thIGNow61uaCB4w6FjIGjGoW4gc28gduG7m2kgYm94cGxvdC4NCg0KRMSpIG5oacOqbiBi4bqhbiBjxaluZyBjw7MgdGjhu4MgZOG7sW5nIGjDrG5oIHRoZW8gY2hp4buBdSBuZ2FuZyBuaMawIHRo4bq/IG7DoHk6DQoNCmBgYHtyfQ0KIGdncGxvdChkZiwgYWVzKHggPSBid3QsIHkgPSByYWNlLCBmaWxsPSByYWNlKSkgKyANCiAgICBnZW9tX2RlbnNpdHlfcmlkZ2VzMihzdGF0ID0gImJpbmxpbmUiLCBkcmF3X2Jhc2VsaW5lID0gRkFMU0UsYWxwaGE9MC42LHNjYWxlID0gMC45KSArDQogICAgZ2VvbV90ZXh0KHN0YXQgPSAiYmluIiwNCiAgICAgICAgICAgICAgYWVzKHkgPSBncm91cCArIDAuOSooLi5jb3VudC4uL21heCguLmNvdW50Li4pKSwNCiAgICAgICAgICAgICAgICAgIGxhYmVsID0gaWZlbHNlKC4uY291bnQuLj4wLCAuLmNvdW50Li4sICIiKSksc2l6ZSA9IDMsIGNvbG9yID0gImJsYWNrIix2anVzdD0xLjUpKw0KICAgIGxhYnMoeD0iQmlydGh3ZWlnaHQiLCB5ID0gIlJhY2UiKSsNCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLjAxLDApKSsNCiAgICBzY2FsZV95X2Rpc2NyZXRlKGV4cGFuZCA9IGMoMC4wMSwgMCkpKw0KICAgIG15X3RoZW1lKDEwKSArDQogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBoanVzdD0xKSkNCmBgYA0KDQojIFPhu60gZOG7pW5nIGRlbnNpdHkgcGxvdCB0aGF5IGNobyBCb3hwbG90DQoNClRyb25nIHRyxrDhu51uZyBo4bujcCBi4bqhbiBjw7MgMSBiaeG6v24gbGnDqm4gdOG7pWMsIGzDumMgbsOgeSBkZW5zaXR5IGN1cnZlIGzDoCBnaeG6o2kgcGjDoXAgcuG6pXQgaOG7o3AgbMO9IMSR4buDIGJp4buDdSBkaeG7hW4gcGjDom4gcGjhu5FpIGPhu6dhIGJp4bq/biBZOyBUdXkgbmhpw6puIG7hur91IGNo4buJIGTDuW5nIGdncGxvdDIsIGLhuqFuIGtow7RuZyB0aOG7gyBzbyBzw6FuaCDEkeG6t2MgdMOtbmggcGjDom4gcGjhu5FpIGPhu6dhIG5oaeG7gXUgcGjDom4gbmjDs20gbcOgIGtow7RuZyBkw7luZyBmYWNldCBwbG90Lg0KDQpnZ3JpZGdlcyBjaG8gcGjDqXAgYuG6oW4gZ2jDqXAgMyBkZW5zaXR5IGN1cnZlIHRoZW8gY2hp4buBdSBk4buNYyBoYXkgY2hp4buBdSBuZ2FuZyB0csOqbiBjw7luZyAxIHBhbmVsOw0KDQpU4burIGtoaSBwaMOhdCBoaeG7h24gZ2dyaWRnZXMsIE5oaSBi4bqvdCDEkeG6p3UgdGhheSB0aOG6vyBCb3hwbG90IGLhurFuZyBkZW5zaXR5IHBsb3QgdGhlbyBjaGnhu4F1IG5nYW5nIGhheSBk4buNYyB0aOG6vyBuw6B5Og0KDQpTbyB24bubaSBCb3hwbG90IHRydXnhu4FuIHRo4buRbmcsIGjDrG5oIHbhur0gMyBkZW5zaXR5cGxvdCBiw6puIHRyw6FpIG1hbmcgbOG6oWkgbmhp4buBdSB0aMO0bmcgdGluIGjGoW4gaOG6s24uIEJveHBsb3QgY2jhu4kgY2hvIHRhIGJp4bq/dCB0aMO0bmcgdGluIHTDs20gdOG6r3QgduG7gSB04bupIHBow6JuIHbhu4ssdHJ1bmcgduG7iyB0cm9uZyBraGkgZGVuc2l0eSBwbG90IGNobyB0YSBow6xuaCDhuqNuaCB24buBIHBow6JuIHBo4buRaSBt4buZdCBjw6FjaCB0cnVuZyB0aOG7sWMgdsOgIGNoaSB0aeG6v3QgxJHhur9uIHThu6tuZyBiw6FjaCBwaMOibiB24buLLiBWaeG7h2MgdGjhu4MgaGnhu4duIG91dGxpZXIgdHLDqm4gQm94cGxvdCBsw6AgY8OzIMOtY2gsIG5oxrBuZyAtIMSRw7RpIGtoaSBuaOG7r25nIGdpw6EgdHLhu4sgbsOgeSBraMO0bmcgcGjhuqNpIGzDoCBvdXRsaWVycywgbcOgIGNo4buJIGzDoCBwaOG6p24gY+G7sWMgdGnhu4N1IGhheSBj4buxYyDEkeG6oWkgY+G7p2EgbeG7mSBxdXkgbHXhuq10IHBow6JuIHBo4buRaSBs4buHY2ggdHLDoWkgaGF5IHBo4bqjaSwgdGjDrSBk4bulIEdhbW1hLi4uDQoNCmBgYHtyfQ0KcTE9Z2dwbG90KGRmLCBhZXMoeCA9IGJ3dCwgeSA9IHNtb2tlLCBmaWxsPSBzbW9rZSkpICsgDQogICAgZ2VvbV9kZW5zaXR5X3JpZGdlcyhzY2FsZSA9IDEuNSxhbHBoYT0wLjYpICsNCiAgICBsYWJzKHg9IkJpcnRod2VpZ2h0IiwgeSA9ICJTbW9raW5nIHN0YXR1cyIpICsgc2NhbGVfZmlsbF9kaXNjcmV0ZShuYW1lID0gIlNtb2tpbmciKSArDQogICAgbXlfdGhlbWUoOCkgKw0KICAgIGdlb21fcnVnKGFlcyhjb2w9c21va2UpLHNob3cubGVnZW5kID0gRikrY29vcmRfZmxpcCgpKw0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpK2ZhY2V0X3dyYXAofnJhY2UpK2dndGl0bGUoIkRlbnNpdHkgY3VydmVzIikNCg0KICBxMj1nZ3Bsb3QoZGYsIGFlcyh5ID0gYnd0LCB4ID0gc21va2UsIGZpbGw9IHNtb2tlKSkgKyANCiAgICBnZW9tX2JveHBsb3QoYWxwaGE9MC44KSArDQogICAgbGFicyh5PSJCaXJ0aHdlaWdodCIsIHggPSAiU21va2luZyBzdGF0dXMiKSArDQogICAgbXlfdGhlbWUoOCkgKw0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpK2ZhY2V0X3dyYXAofnJhY2UpK2dndGl0bGUoIlR1a2V5J3MgQm94cGxvdCIpDQogIA0KICBncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShxMSxxMixuY29sPTIpDQogIA0KYGBgDQoNCiMgVMO0IG3DoHUgdGhlbyBncmFkaWVudCBjaG8gZGVuc2l0eXBsb3QNCg0KS2jDtG5nIGNo4buJIGNobyBwaMOpcCB0w6FjaCBiaeG7h3QgdsOgIMSR4bqjbyBjaGnhu4F1IG5o4buvbmcgZGVuc2l0eSBwbG90LCBnZ3JpZGdlcyAyIGPDsm4gY2hvIHBow6lwIMOhcCBk4bulbmcgZ3JhZGllbnQgbcOgdSBjaG8gY8OhYyBkZW5zaXR5cGxvdCBuw6B5Lg0KDQpUcsaw4bubYyBraWEsIGdncGxvdDIgY2jhu4kgY2hvIHBow6lwIGTDuW5nIGdyYWRpZW50IG3DoHUgY2hvIGdlb21fcG9pbnQsIHNjYWxlX2ZpbGxfZ3JhZGllbnQgaGF5IHNjYWxlX2ZpbGxfY29udGludW91cyBraMO0bmcgdGjhu4MgZMO5bmcgY2hvIG5o4buvbmcgaMOsbmggY8OzIGRp4buHbiB0w61jaCwgbmjGsCBib3hwbG90LCB2aW9saW4gcGxvdCBoYXkgZGVuc2l0eSBwbG90LiBCw6J5IGdp4budIHRow6wgYuG6oW4gY8OzIHRo4buDIHTDtCBtw6B1IGdyYWRpZW50IGNobyBj4bqjIGRlbnNpdHkgcGxvdCA6IEjDrG5oIHbhur0gxJHhurlwIGjGoW4gdsOgIGfDonkg4bqlbiB0xrDhu6NuZyBt4bqhbmggaMahbi4NCg0KYGBge3J9DQpzMT1nZ3Bsb3QoZGYsIGFlcyh4ID0gYnd0LCB5ID0gcmFjZSxmaWxsID0gLi54Li4pKSsgDQogICAgZ2VvbV9kZW5zaXR5X3JpZGdlc19ncmFkaWVudChzY2FsZSA9IDEsYWxwaGE9MC41LGdyYWRpZW50X2x3ZCA9IDAuNSxzaG93LmxlZ2VuZCA9IEYpICsNCiAgICBsYWJzKHg9IkJpcnRod2VpZ2h0IiwgeSA9ICJSYWNlIikgKw0KICAgIG15X3RoZW1lKDgpKw0KICAgIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAuMDEsIDApKSsNCiAgICBzY2FsZV95X2Rpc2NyZXRlKGV4cGFuZCA9IGMoMC4wMSwgMCkpKw0KICAgIG15X3RoZW1lKDgpKw0KICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb249IkMiKSsNCiAgY29vcmRfZmxpcCgpK2dndGl0bGUoIkdyYWRpZW50IGRlbnNpdHkgcGxvdHMiKQ0KDQpzMj1nZ3Bsb3QoZGYsIGFlcyh5PWJ3dCx4PXJhY2UsZmlsbD1id3QpKSsgDQogIGdlb21fcG9pbnQoc2hhcGU9MjEsYWVzKGNvbD1id3Qsc2l6ZT1id3QpLGFscGhhPTAuNixzaG93LmxlZ2VuZCA9IEYpICsNCiAgbGFicyh5PSJCaXJ0aHdlaWdodCIsIHggPSAiUmFjZSIpICsNCiAgbXlfdGhlbWUoOCkrDQogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb249IkMiKSsNCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXMob3B0aW9uPSJDIikrZ2d0aXRsZSgiZ2VvbV9wb2ludCIpDQoNCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHMxLHMyLG5jb2w9MikNCg0KICANCmBgYA0KDQojIEvhur90IGx14bqtbg0KDQpC4bqhbiBjw7MgdGjhu4MgdOG6o2kgcGFja2FnZSBnZ3JpZGdlcyB04burIENSQU46IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nZ3JpZGdlcy9pbmRleC5odG1sDQpWw6AgZMO5bmcgdGjhu60uIE5oaSB0aW4gY2jhuq9jIHNhdSBraGkgxJHhu41jIGLDoGkgbsOgeSB2w6Agw6FwIGThu6VuZyBnZ3JpZGdlcywgY8OhYyBi4bqhbiBz4bq9IG5oYW5oIGNow7NuZyBjaMOhbiBCb3hwbG90IHbDoCB0aGF5IHRo4bq/IGNow7puZyBi4bqxbmcgZGVuc2l0eXBsb3Qga2hpIGzDoG0gdGjhu5FuZyBrw6ogbcO0IHThuqMvIHRoxINtIGTDsiBk4buvIGxp4buHdS4NCg0KTmjhu69uZyBk4bqhbmcgYmnhu4N1IMSR4buTIG7DoHkgY8WpbmcgY+G7sWMga8OsIGjhu691IMOtY2gga2hpIGLhuqFuIGzDoG0gYm9vdHN0cmFwIHbDoCBwaMOibiB0w61jaCBCYXllcy4gDQoNClThuqFtIGJp4buHdCBjw6FjIGLhuqFuDQo=