필요한 패키지들을 깐다.
list("ggplot2", "plyr", "dplyr", "car") %>%
map(library)
Error in .f(.x[[i]], ...) : 'package' must be of length 1
필요한 라이브러리들을 불러들인다.
requiredPackages <- c("MASS", "purrr", "ggplot2", "plyr","dplyr","car")
ipak <- function(pkg){
new.pkg <- pkg[!(pkg %in% installed.packages()[, "Package"])]
if (length(new.pkg))
install.packages(new.pkg, dependencies = TRUE)
sapply(pkg, require, character.only = TRUE)
}
ipak(requiredPackages)
Loading required package: MASS
Attaching package: 愼㸱愼㹥MASS愼㸱愼㹦
The following object is masked from 愼㸱愼㹥package:dplyr愼㸱愼㹦:
select
Loading required package: plyr
-----------------------------------------------------------------------------
You have loaded plyr after dplyr - this is likely to cause problems.
If you need functions from both plyr and dplyr, please load plyr first, then dplyr:
library(plyr); library(dplyr)
-----------------------------------------------------------------------------
Attaching package: 愼㸱愼㹥plyr愼㸱愼㹦
The following object is masked from 愼㸱愼㹥package:purrr愼㸱愼㹦:
compact
The following objects are masked from 愼㸱愼㹥package:dplyr愼㸱愼㹦:
arrange, count, desc, failwith, id, mutate, rename, summarise,
summarize
Loading required package: car
Loading required package: carData
Attaching package: 愼㸱愼㹥car愼㸱愼㹦
The following object is masked from 愼㸱愼㹥package:purrr愼㸱愼㹦:
some
The following object is masked from 愼㸱愼㹥package:dplyr愼㸱愼㹦:
recode
MASS purrr ggplot2 plyr dplyr car
TRUE TRUE TRUE TRUE TRUE TRUE
각 데이터의 행과 열을 살펴보자
library(purrr)
list(products,search1, search2, custom) %>%
map(dim)
[[1]]
[1] 5024906 8
[[2]]
[1] 2884943 4
[[3]]
[1] 8051172 3
[[4]]
[1] 671679 3
Custom테이블 살펴보기
dim(custom) # 67만명의 데이터가 있다. 671,679
[1] 671679 3
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
input string 1 is invalid in this locale
고객들의 분포를 살펴보자
table(custom$CLNT_AGE, custom$CLNT_GENDER)
F M
10 3007 800
20 82269 13028
30 271712 41888
40 180398 35819
50 28881 7743
60 3784 1472
70 542 304
80 23 9
tmp6 <- table(custom$CLNT_AGE, custom$CLNT_GENDER)
tmp6 <- as.data.frame(tmp6)
colnames(tmp6)[1:2] <- c("age","gender")
library(ggplot2)
ggplot(tmp6, aes(age, Freq, group=gender, col=gender)) +
geom_line()

- 여성 고객의 비율이 많다.
- 30대 여성의 비율이 가장 높다.
producst테이블 살펴보기 ——————————————————–
Sys.setlocale("LC_ALL","Korean")
[1] "LC_COLLATE=Korean_Korea.949;LC_CTYPE=Korean_Korea.949;LC_MONETARY=Korean_Korea.949;LC_NUMERIC=C;LC_TIME=Korean_Korea.949"
products <- as.data.frame(products)
dim(products) # 500만건
[1] 5024906 8
colnames(products)
[1] "CLNT_ID" "SESS_ID" "HITS_SEQ" "PD_C" "PD_ADD_NM" "PD_BRA_NM"
[7] "PD_BUY_AM" "PD_BUY_CT"
head(products)
products$SESS_ID <- sprintf("%08d", as.integer(products$SESS_ID)) #
# tmp <- products[order(products$HITS_SEQ), ] # hits sequence에 따라 정렬
# tmp[order(tmp$SESS_ID), ]
동일한 세션 아이디가 몇개 있는지 살펴보자
고유한 세션 아이디가 전체의 48프로. 52프로는 반복된 세션이라는 말…
length(unique(products$SESS_ID)) / nrow(products)
[1] 0.4827724
master테이블 살펴보기 ———————————————————-
head(master)
Error in head(master) : object 'master' not found
search1 <- as.data.frame(search1)
search2 <- as.data.frame(search2)
# search 테이블 살펴보기 ---------------------------------------------------------
하루당 검색건수를 알아보자.
counts <- search2 %>%
group_by(SESS_DT) %>%
dplyr::summarise(searching_occured = n(), number_of_na = sum(is.na()) )
Error in summarise_impl(.data, dots) :
Evaluation error: 0 arguments passed to 'is.na' which requires 1.
YYYYMMDD 형태를 날짜 변수로 바꾸는 방법.
counts$SESS_DT <- as.Date(as.character(counts[["SESS_DT"]]), "%Y%m%d")
counts <- as.data.frame(counts)
그래프로 그려보자.
datebreaks <- seq(min(counts$SESS_DT), max(counts$SESS_DT), by="1 month")
library(scales)
ggplot(counts, aes(SESS_DT, searching_occured)) +
geom_line()+
scale_x_date(breaks = datebreaks, labels = date_format("%Y%m"))+
theme(axis.text.x = element_text(angle=30, hjust=1))

일주일에 한번씩 검색량의 등락이 보인다. 총 26번의 등락이 있고, 이는 6개월(26주)를 의미한다.
검색어의 길이에 따라 검색량에 차이가 있는지 살펴보자.
검색어 길이가 긴 것은 A로 정의하고, 짧은 그룹을 B라고 정의하자. 검색어 길이가 길면 길수록 특수성이 올라가기 때문에, hit수가 적을 것으로 추정된다. 실제로 그런가, 검정해보자.
tmp[tmp$cnt <= 5, ]$group <- "B"
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
input string 1 is invalid in this locale
t-검정을 실시해보자.
t.test(chr ~ group, data = tmp, var.equal=TRUE)
Two Sample t-test
data: chr by group
t = -113.58, df = 8050600, p-value < 2.2e-16
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
-0.2115216 -0.2043454
sample estimates:
mean in group A mean in group B
5.867973 6.075906
두 집단 사이에 차이가 있는 것으로 보인다.

Session 테이블을 살펴보자
head(session)
session <- as.data.frame(session)
sort(table(session$ZON_NM), decreasing = T)
Seoul Gyeonggi-do Busan Gyeongsangnam-do
1019843 469954 295940 128716
Daegu Incheon Gyeongsangbuk-do Jeollabuk-do
116707 100728 91183 78437
Chungcheongnam-do Gwangju Daejeon Jeollanam-do
78072 70773 60442 49299
Ulsan Chungcheongbuk-do Gangwon-do Jeju-do
46584 44455 42701 19073
plot(sort(table(session$ZON_NM), decreasing = T))

한 세션당 클릭수는 얼마나 되나 살펴보자. 카이제곱 분포 모양과 흡사하다.

얼마나 오래 머무는지 살펴보자.
plot(sort(table(session$TOT_SESS_HR_V), decreasing = T))
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
input string 1 is invalid in this locale
abline(v=1800, col="red") # 세션이 만료되는 30분 지점.

plot(sort(table(session$TOT_SESS_HR_V), decreasing = T), xlim=c(0,1440))

한 세션에 39329초(32329/60 = 655시간) 머무는 데이터가 있다. 아웃라이어로 간주된다. 아니면 매크로 봇이거나….
날짜별 접속한 세션들의 수(고객수)를 집계해보자.
counting_table <- plyr::count(session, 'SESS_DT')
datebreaks <- seq(min(counting_table$SESS_DT), max(counting_table$SESS_DT), by="1 month")
library(scales)
ggplot(counting_table, aes(SESS_DT,freq)) +
geom_line()+
scale_x_date(breaks = datebreaks, labels = date_format("%Y%m"))+
theme(axis.text.x = element_text(angle=30, hjust=1))

기기별 사용자수를 살펴보자.
모바일 기기 사용자수가 제일 많다.

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdGhlbWU6IHBhcGVyDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQotLS0NCg0K7ZWE7JqU7ZWcIO2MqO2CpOyngOuTpOydhCDquZDri6QuIA0KDQpgYGB7cn0NCndhbnRzIDwtIGMoICJkcGx5ciIsImdncGxvdDIiLCJwdXJyciIgKQ0KaGFzICAgPC0gd2FudHMgJWluJSByb3duYW1lcyhpbnN0YWxsZWQucGFja2FnZXMoKSkNCmlmKGFueSghaGFzKSkgaW5zdGFsbC5wYWNrYWdlcyh3YW50c1shaGFzXSkNCg0KDQoNCmBgYA0KDQrtlYTsmpTtlZwg65287J2067iM65+s66as65Ok7J2EIOu2iOufrOuTpOyduOuLpC4gDQpgYGB7cn0NCg0KcmVxdWlyZWRQYWNrYWdlcyA8LSBjKCJNQVNTIiwgInB1cnJyIiwgImdncGxvdDIiLCAicGx5ciIsImRwbHlyIiwiY2FyIikNCg0KDQppcGFrIDwtIGZ1bmN0aW9uKHBrZyl7DQogICAgICAgIG5ldy5wa2cgPC0gcGtnWyEocGtnICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLCAiUGFja2FnZSJdKV0NCiAgICAgICAgaWYgKGxlbmd0aChuZXcucGtnKSkNCiAgICAgICAgICAgICAgICBpbnN0YWxsLnBhY2thZ2VzKG5ldy5wa2csIGRlcGVuZGVuY2llcyA9IFRSVUUpDQogICAgICAgIHNhcHBseShwa2csIHJlcXVpcmUsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCn0NCg0KaXBhayhyZXF1aXJlZFBhY2thZ2VzKQ0KYGBgDQoNCg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBldmFsPUZBTFNFfQ0KDQoNCiMgMjAxOOyekOujjCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KbGlicmFyeShyZWFkcikNCnByb2R1Y3RzIDwtIHJlYWRfY3N2KCJFOi9Ecm9wYm94LzIwMTcvMDYuam9iX3JlY3J1aXRtZW50LzEzLnByb2dyYW1taW5nLzA1Lkxwb2ludC9kYXRhLzIwMTgvMDEuUHJ1ZHVjdC5jc3YiLA0KICBsb2NhbGUgPSBsb2NhbGUoKQ0KKQ0Kc2VhcmNoMSA8LSByZWFkX2NzdigiRTovRHJvcGJveC8yMDE3LzA2LmpvYl9yZWNydWl0bWVudC8xMy5wcm9ncmFtbWluZy8wNS5McG9pbnQvZGF0YS8yMDE4LzAyLlNlYXJjaDEuY3N2IiwNCiAgbG9jYWxlID0gbG9jYWxlKCkNCikNCnNlYXJjaDIgPC0gcmVhZF9jc3YoIkU6L0Ryb3Bib3gvMjAxNy8wNi5qb2JfcmVjcnVpdG1lbnQvMTMucHJvZ3JhbW1pbmcvMDUuTHBvaW50L2RhdGEvMjAxOC8wMy5TZWFyY2gyLmNzdiIsDQogIGxvY2FsZSA9IGxvY2FsZSgpDQopDQpjdXN0b20gPC0gcmVhZF9jc3YoIkU6L0Ryb3Bib3gvMjAxNy8wNi5qb2JfcmVjcnVpdG1lbnQvMTMucHJvZ3JhbW1pbmcvMDUuTHBvaW50L2RhdGEvMjAxOC8wNC5DdXN0b20uY3N2IiwNCiAgbG9jYWxlID0gbG9jYWxlKCkNCikNCnNlc3Npb24gPC0gcmVhZF9jc3YoIkU6L0Ryb3Bib3gvMjAxNy8wNi5qb2JfcmVjcnVpdG1lbnQvMTMucHJvZ3JhbW1pbmcvMDUuTHBvaW50L2RhdGEvMjAxOC8wNS5TZXNzaW9uLmNzdiIsDQogIGxvY2FsZSA9IGxvY2FsZSgpDQopDQptYXN0ZXIgPC0gcmVhZF9jc3YoIkU6L0Ryb3Bib3gvMjAxNy8wNi5qb2JfcmVjcnVpdG1lbnQvMTMucHJvZ3JhbW1pbmcvMDUuTHBvaW50L2RhdGEvMjAxOC8wNi5NYXN0ZXIuY3N2IiwNCiAgbG9jYWxlID0gbG9jYWxlKCkNCikNCg0KYGBgDQoNCg0K6rCBIOuNsOydtO2EsOydmCDtlonqs7wg7Je07J2EIOyCtO2OtOuztOyekA0KYGBge3J9DQoNCmxpYnJhcnkocHVycnIpDQpsaXN0KHByb2R1Y3RzLHNlYXJjaDEsIHNlYXJjaDIsIGN1c3RvbSkgJT4lIA0KICBtYXAoZGltKQ0KYGBgDQoNCiMgQ3VzdG9t7YWM7J2067iUIOyCtO2OtOuztOq4sA0KDQpgYGB7cn0NCg0KIyBjdXN0b23thYzsnbTruJQg7IK07Y6067O06riwIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCmN1c3RvbSA8LSBhcy5kYXRhLmZyYW1lKGN1c3RvbSkNCg0KZGltKGN1c3RvbSkgIyA2N+unjOuqheydmCDrjbDsnbTthLDqsIAg7J6I64ukLiAgNjcxLDY3OQ0KDQpgYGANCg0KIyMg6rOg6rCd65Ok7J2YIOu2hO2PrOulvCDsgrTtjrTrs7TsnpAgDQoNCmBgYHtyfQ0KdGFibGUoY3VzdG9tJENMTlRfQUdFLCBjdXN0b20kQ0xOVF9HRU5ERVIpDQp0bXA2IDwtIHRhYmxlKGN1c3RvbSRDTE5UX0FHRSwgY3VzdG9tJENMTlRfR0VOREVSKQ0KdG1wNiA8LSBhcy5kYXRhLmZyYW1lKHRtcDYpDQpjb2xuYW1lcyh0bXA2KVsxOjJdIDwtIGMoImFnZSIsImdlbmRlciIpDQpsaWJyYXJ5KGdncGxvdDIpDQpnZ3Bsb3QodG1wNiwgYWVzKGFnZSwgRnJlcSwgZ3JvdXA9Z2VuZGVyLCBjb2w9Z2VuZGVyKSkgKw0KICBnZW9tX2xpbmUoKQ0KDQpgYGANCi0g7Jes7ISxIOqzoOqwneydmCDruYTsnKjsnbQg66eO64ukLg0KLSAzMOuMgCDsl6zshLHsnZgg67mE7Jyo7J20IOqwgOyepSDrhpLri6QuIA0KDQoNCiMgcHJvZHVjc3TthYzsnbTruJQg7IK07Y6067O06riwIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpgYGB7cn0NClN5cy5zZXRsb2NhbGUoIkxDX0FMTCIsIktvcmVhbiIpDQoNCnByb2R1Y3RzIDwtIGFzLmRhdGEuZnJhbWUocHJvZHVjdHMpDQpkaW0ocHJvZHVjdHMpICMgNTAw66eM6rG0DQpjb2xuYW1lcyhwcm9kdWN0cykNCmhlYWQocHJvZHVjdHMpDQoNCmBgYA0KDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQoNCnByb2R1Y3RzJFNFU1NfSUQgPC0gc3ByaW50ZigiJTA4ZCIsIGFzLmludGVnZXIocHJvZHVjdHMkU0VTU19JRCkpICMNCg0KIyB0bXAgPC0gcHJvZHVjdHNbb3JkZXIocHJvZHVjdHMkSElUU19TRVEpLCBdICMgaGl0cyBzZXF1ZW5jZeyXkCDrlLDrnbwg7KCV66CsDQojIHRtcFtvcmRlcih0bXAkU0VTU19JRCksIF0NCg0KDQpgYGANCg0KDQojIyDrj5nsnbztlZwg7IS47IWYIOyVhOydtOuUlOqwgCDrqofqsJwg7J6I64qU7KeAIOyCtO2OtOuztOyekA0K6rOg7Jyg7ZWcIOyEuOyFmCDslYTsnbTrlJTqsIAg7KCE7LK07J2YIDQ47ZSE66GcLiA1Mu2UhOuhnOuKlCDrsJjrs7XrkJwg7IS47IWY7J20652864qUIOunkC4uLg0KYGBge3J9DQpsZW5ndGgodW5pcXVlKHByb2R1Y3RzJFNFU1NfSUQpKSAvIG5yb3cocHJvZHVjdHMpIA0KYGBgDQoNCg0KIyBtYXN0ZXLthYzsnbTruJQg7IK07Y6067O06riwIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KYGBge3J9DQoNCmhlYWQobWFzdGVyKQ0KIyBzdHIobWFzdGVyKQ0KDQpgYGANCg0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KDQpzZWFyY2gxIDwtIGFzLmRhdGEuZnJhbWUoc2VhcmNoMSkNCnNlYXJjaDIgPC0gYXMuZGF0YS5mcmFtZShzZWFyY2gyKQ0KDQojIHNlYXJjaCDthYzsnbTruJQg7IK07Y6067O06riwIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQoNCmBgYA0KDQoNCg0K7ZWY66Oo64u5IOqygOyDieqxtOyImOulvCDslYzslYTrs7TsnpAuIA0KYGBge3J9DQpsaWJyYXJ5KGRwbHlyKQ0KDQpjb3VudHMgPC0gc2VhcmNoMiAlPiUgDQogIGdyb3VwX2J5KFNFU1NfRFQpICU+JSANCiAgZHBseXI6OnN1bW1hcmlzZShzZWFyY2hpbmdfb2NjdXJlZCA9IG4oKSApDQoNCg0KYGBgDQoNCg0KIyMjIyBZWVlZTU1ERCDtmJXtg5zrpbwg64Kg7KecIOuzgOyImOuhnCDrsJTqvrjripQg67Cp67KVLiANCg0KYGBge3J9DQpjb3VudHMkU0VTU19EVCA8LSBhcy5EYXRlKGFzLmNoYXJhY3Rlcihjb3VudHNbWyJTRVNTX0RUIl1dKSwgIiVZJW0lZCIpDQpjb3VudHMgPC0gYXMuZGF0YS5mcmFtZShjb3VudHMpDQpgYGANCg0KDQoNCuq3uOuemO2UhOuhnCDqt7jroKTrs7TsnpAuIA0KYGBge3J9DQoNCg0KZGF0ZWJyZWFrcyA8LSBzZXEobWluKGNvdW50cyRTRVNTX0RUKSwgbWF4KGNvdW50cyRTRVNTX0RUKSwgYnk9IjEgbW9udGgiKQ0KDQpsaWJyYXJ5KHNjYWxlcykNCmdncGxvdChjb3VudHMsIGFlcyhTRVNTX0RULCBzZWFyY2hpbmdfb2NjdXJlZCkpICsNCiAgZ2VvbV9saW5lKCkrDQogIHNjYWxlX3hfZGF0ZShicmVha3MgPSBkYXRlYnJlYWtzLCBsYWJlbHMgPSBkYXRlX2Zvcm1hdCgiJVklbSIpKSsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9MzAsIGhqdXN0PTEpKQ0KDQpgYGANCuydvOyjvOydvOyXkCDtlZzrsojslKkg6rKA7IOJ65+J7J2YIOuTseudveydtCDrs7Tsnbjri6QuIA0K7LSdIDI267KI7J2YIOuTseudveydtCDsnojqs6AsIOydtOuKlCA26rCc7JuUKDI27KO8KeulvCDsnZjrr7jtlZzri6QuIA0KDQoNCiMjIyDqsoDsg4nslrTsnZgg6ri47J207JeQIOuUsOudvCDqsoDsg4nrn4nsl5Ag7LCo7J206rCAIOyeiOuKlOyngCDsgrTtjrTrs7TsnpAuIA0KDQrqsoDsg4nslrQg6ri47J206rCAIOq4tCDqsoPsnYAgQeuhnCDsoJXsnZjtlZjqs6AsIOynp+ydgCDqt7jro7nsnYQgQuudvOqzoCDsoJXsnZjtlZjsnpAuIA0K6rKA7IOJ7Ja0IOq4uOydtOqwgCDquLjrqbQg6ri47IiY66GdIO2KueyImOyEseydtCDsmKzrnbzqsIDquLAg65WM66y47JeQLCBoaXTsiJjqsIAg7KCB7J2EIOqyg+ycvOuhnCDstpTsoJXrkJzri6QuIOyLpOygnOuhnCDqt7jrn7DqsIAsIOqygOygle2VtOuztOyekC4gDQoNCmBgYHtyfQ0KdG1wIDwtIGRhdGEuZnJhbWUoDQogIGNudCA9IHNlYXJjaDIkU0VBUkNIX0NOVCwNCiAgY2hyID0gYXBwbHkoc2VhcmNoMiwgMiwgbmNoYXIpWywyXQ0KKQ0KDQojIOqygOyDieufieyXkCDrlLDrpbgg6rKA7IOJ7Ja0IOq4uOydtCDssKjsnbTqsIAg7J6I64qU7KeAIOyCtO2OtOuztOyekC4NCnRtcCA8LSB0bXBbY29tcGxldGUuY2FzZXModG1wKSxdDQp0bXAgPC0gYXMuZGF0YS5mcmFtZSh0bXApDQp0bXAkZ3JvdXAgPC0gTkENCnRtcFt0bXAkY250ID4gNSwgXSRncm91cCA8LSAiQSINCnRtcFt0bXAkY250IDw9IDUsIF0kZ3JvdXAgPC0gIkIiDQoNCmBgYA0KDQoNCnQt6rKA7KCV7J2EIOyLpOyLnO2VtOuztOyekC4gDQoNCmBgYHtyICB9DQp0LnRlc3QoY2hyIH4gZ3JvdXAsIGRhdGEgPSB0bXAsIHZhci5lcXVhbD1UUlVFKQ0KDQoNCmBgYA0K65GQIOynkeuLqCDsgqzsnbTsl5Ag7LCo7J206rCAIOyeiOuKlCDqsoPsnLzroZwg67O07J2464ukLiANCg0KDQpgYGB7cn0NCmdncGxvdCh0bXAsIGFlcyhjaHIsIGNvbD1ncm91cCkpICsNCiAgZ2VvbV9kZW5zaXR5KGFkanVzdD01KSsNCiAgeGxpbSgwLDIwKQ0KYGBgDQoNCmBgYHtyfQ0KaGVhZChwcm9kdWN0cykNCmhlYWQoc2VhcmNoMSkNCmhlYWQoc2VhcmNoMikNCg0KYGBgDQoNCg0KDQojIFNlc3Npb24g7YWM7J2067iU7J2EIOyCtO2OtOuztOyekA0KYGBge3J9DQpoZWFkKHNlc3Npb24pDQoNCnNlc3Npb24kU0VTU19EVCA8LSBhcy5EYXRlKGFzLmNoYXJhY3RlcihzZXNzaW9uW1siU0VTU19EVCJdXSksICIlWSVtJWQiKQ0Kc2Vzc2lvbiA8LSBhcy5kYXRhLmZyYW1lKHNlc3Npb24pDQoNCnNvcnQodGFibGUoc2Vzc2lvbiRaT05fTk0pLCBkZWNyZWFzaW5nID0gVCkNCnBsb3Qoc29ydCh0YWJsZShzZXNzaW9uJFpPTl9OTSksIGRlY3JlYXNpbmcgPSBUKSkNCg0KYGBgDQoNCiMjIO2VnCDshLjshZjri7kg7YG066at7IiY64qUIOyWvOuniOuCmCDrkJjrgpgg7IK07Y6067O07J6QLiDsubTsnbTsoJzqs7Eg67aE7Y+sIOuqqOyWkeqzvCDtnaHsgqztlZjri6QuIA0KYGBge3J9DQpwbG90KHNvcnQodGFibGUoc2Vzc2lvbiRUT1RfUEFHX1ZJRVdfQ1QpLCBkZWNyZWFzaW5nID0gVCkpDQoNCmBgYA0KDQrslrzrp4jrgpgg7Jik656YIOuouOustOuKlOyngCDsgrTtjrTrs7TsnpAuIA0KYGBge3J9DQpwbG90KHNvcnQodGFibGUoc2Vzc2lvbiRUT1RfU0VTU19IUl9WKSwgZGVjcmVhc2luZyA9IFQpKQ0KYWJsaW5lKHY9MTgwMCwgY29sPSJyZWQiKSAjIOyEuOyFmOydtCDrp4zro4zrkJjripQgMzDrtoQg7KeA7KCQLiANCg0KcGxvdChzb3J0KHRhYmxlKHNlc3Npb24kVE9UX1NFU1NfSFJfViksIGRlY3JlYXNpbmcgPSBUKSwgeGxpbT1jKDAsMTQ0MCkpDQoNCmBgYA0K7ZWcIOyEuOyFmOyXkCAzOTMyOey0iCgzMjMyOS82MCA9IDY1NeyLnOqwhCkg66i466y064qUIOuNsOydtO2EsOqwgCDsnojri6QuIA0K7JWE7JuD65287J207Ja066GcIOqwhOyjvOuQnOuLpC4g7JWE64uI66m0IOunpO2BrOuhnCDrtIfsnbTqsbDrgpguLi4uDQoNCg0KIyMg64Kg7Kec67OEIOygkeyGje2VnCDshLjshZjrk6TsnZgg7IiYKOqzoOqwneyImCnrpbwg7KeR6rOE7ZW067O07J6QLiANCmBgYHtyfQ0KDQpjb3VudGluZ190YWJsZSA8LSBwbHlyOjpjb3VudChzZXNzaW9uLCAnU0VTU19EVCcpDQoNCmRhdGVicmVha3MgPC0gc2VxKG1pbihjb3VudGluZ190YWJsZSRTRVNTX0RUKSwgbWF4KGNvdW50aW5nX3RhYmxlJFNFU1NfRFQpLCBieT0iMSBtb250aCIpDQoNCmxpYnJhcnkoc2NhbGVzKQ0KZ2dwbG90KGNvdW50aW5nX3RhYmxlLCBhZXMoU0VTU19EVCxmcmVxKSkgKw0KICBnZW9tX2xpbmUoKSsNCiAgc2NhbGVfeF9kYXRlKGJyZWFrcyA9IGRhdGVicmVha3MsIGxhYmVscyA9IGRhdGVfZm9ybWF0KCIlWSVtIikpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT0zMCwgaGp1c3Q9MSkpDQoNCg0KYGBgDQoNCiMjIOq4sOq4sOuzhCDsgqzsmqnsnpDsiJjrpbwg7IK07Y6067O07J6QLiANCg0K66qo67CU7J28IOq4sOq4sCDsgqzsmqnsnpDsiJjqsIAg7KCc7J28IOunjuuLpC4gDQoNCmBgYHtyfQ0KYmFycGxvdCh0YWJsZShzZXNzaW9uJERWQ19DVEdfTk0pKQ0KDQoNCg0KYGBgDQoNCg0KDQoNCg0K