Ch.0 : 資料取得及觀念複習

1. 資料取得及套件載入

載入的資料是由中山大學管理學院文字分析平台取得,在平台資料輸出區塊選擇「文章+詞彙+詞頻」選項,即可取得相同格式之csv檔案。

資料簡介

本資料為2019/01/01 ~ 2019/03/04 PTT八卦版之資料,透過文字分析平台檢索「還願」、「美心」兩個關鍵字,共搜尋到683篇文章。

Sys.setlocale(category = "LC_ALL", locale = "zh_TW.UTF-8") # 避免中文亂碼
[1] "zh_TW.UTF-8/zh_TW.UTF-8/zh_TW.UTF-8/C/zh_TW.UTF-8/zh_TW.UTF-8"

安裝需要的packages

packages = c("dplyr","ggplot2", "data.table", "scales")
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)
require(ggplot2)
require(dplyr)
require(data.table)
require(scales)
csv <- fread("./data/social_media_0305_artWordFreq.csv", encoding = "UTF-8")
csv$artDate = csv$artDate %>% as.Date("%Y/%m/%d")
str(csv)
Classes ‘data.table’ and 'data.frame':  34501 obs. of  6 variables:
 $ artTitle: chr  "[新聞]萌妹子香汗淋漓 彎腰向四面佛還願" "[新聞]萌妹子香汗淋漓 彎腰向四面佛還願" "[新聞]萌妹子香汗淋漓 彎腰向四面佛還願" "[新聞]萌妹子香汗淋漓 彎腰向四面佛還願" ...
 $ artDate : Date, format: "2019-01-03" "2019-01-03" "2019-01-03" ...
 $ artTime : chr  "14:50:55" "14:50:55" "14:50:55" "14:50:55" ...
 $ artUrl  : chr  "https://www.ptt.cc/bbs/Gossiping/M.1546556219.A.A7A.html" "https://www.ptt.cc/bbs/Gossiping/M.1546556219.A.A7A.html" "https://www.ptt.cc/bbs/Gossiping/M.1546556219.A.A7A.html" "https://www.ptt.cc/bbs/Gossiping/M.1546556219.A.A7A.html" ...
 $ word    : chr  "四面佛" "還願" "越來越" "舞者" ...
 $ count   : int  7 4 2 2 2 1 1 1 1 1 ...
 - attr(*, ".internal.selfref")=<externalptr> 

資料預設之日期欄位是“chr”格式,在畫圖前我們須先將其轉為“date”格式,轉換後可以透過str指令來確認欄位型態。

head(csv)

資料欄位

  1. artTitle: 文章之標題,注意:不同文章可能會有完全相同的標題。
  2. artDate: 文章發佈之日期。
  3. artTime: 文章發佈之時間。
  4. artUrl: 文章之網址,每篇文章之網址為獨一無二的,可用來辨識相同標題之不同文章。
  5. word: 詞彙。
  6. count: 詞頻。

Ch.1 日期折線圖

這個章節的目的是計算出每一天文章的發表數量,可以看出特定主題討論的熱度。

資料處理

data <- csv %>% 
  select(artDate, artUrl) %>% 
  distinct()

由於這份資料的每一列是特定文章的每一個詞彙,我們只需要文章以及日期兩個欄位即可,其他重複欄位可以去除。(一篇文章有很多個詞彙,所以會有很多列,但我們只需要保留一個URL即可)。

article_count_by_date <- data %>% 
  group_by(artDate) %>% 
  summarise(count = n())
head(article_count_by_date, 20)

按照日期分群,計算每天共有幾篇討論文章。

plot_date <- 
  # data
  article_count_by_date %>% 
  # aesthetics
  ggplot(aes(x = artDate, y = count)) +
  # geometrics
  geom_line(color = "#00AFBB", size = 2) + 
  geom_vline(xintercept = as.numeric(as.Date("2019-02-15")), col='red') + 
  # coordinates
  scale_x_date(labels = date_format("%Y/%m/%d")) +
  ggtitle("還願 討論文章數") + 
  xlab("日期") + 
  ylab("數量") + 
  # theme
  theme(text = element_text(family = "Heiti TC Light")) #加入中文字型設定,避免中文字顯示錯誤。
plot_date

從上圖中可以看到關於「還願」的討論從02/15開始加溫,對照維基百科簡介可得知此款遊戲於2019/02/19正式上市。

Ch.2 文字雲

接下來我們來大略觀察討論的內容為何,使用的方式為文字雲。

data <- csv %>% 
  group_by(word) %>% 
  summarise(sum = sum(count)) %>% 
  arrange(desc(sum))

先將資料集中所有文章按照文字進行分群,計算每一個字的總詞頻。

head(data)

結果為總詞頻最多的字。

require(wordcloud2)
data %>% wordcloud2()

將整理好的資料直接送入wordcloud2 function即可得到文字雲。此套件為互動式介面,使用滑鼠移動到特定的詞彙,畫面會同時顯示詞彙以及對應的詞頻。

按照日期進行區分

在Ch.1中的日期折線圖中,我們發現在2/22前後討論數量有一波轉折,我們來看看2/22前後的討論內容是否有不同的地方。

data_before <- csv %>% filter(artDate <= "2019-02-22")
data_after <- csv %>% filter(artDate > "2019-02-22")

按照日期切分資料

data_before <- data_before %>% 
  group_by(word) %>% 
  summarise(sum = sum(count)) %>% 
  arrange(desc(sum))
data_after <- data_after %>% 
  group_by(word) %>% 
  summarise(sum = sum(count)) %>% 
  arrange(desc(sum))

與Ch.2相同的資料處理。

plot_before <- data_before %>% wordcloud2()
plot_before

2/22 之前的文字雲

plot_after <- data_after %>% wordcloud2()
plot_after

2/22之後的文字雲

觀察兩張圖片可以發現,在2/22以前,主要討論為「實況」、「劇情」、「美心」等遊戲本身內容,或是與「返校」等相關作品之比較。 在2/22以後,開始出現大量「中國」、「台灣」、「習近平」、「符咒」、「小熊維尼」等關鍵字。對照維基百科簡介可得知此款遊戲於2019/2/21爆發符咒爭議事件,導致討論焦點開始模糊。

練習練習~

可以發現本次資料集是使用「劇情」、「美心」為關鍵字抓的,所以理所當然「劇情」與「美心」的詞頻會很高。
那麼就請各位將這兩個關鍵字移除後重新繪製文字雲吧!

### Code Here ###

Ch3. 長條圖

文字雲可以直覺看出較常提到的字,但如果想得到精確的「最常出現詞彙」,我們則可以透過長條圖來查看。

data <- rbind(data_before %>% mutate(type="before"), data_after %>% mutate(type="after"))

將2/22前後的資料加入一個欄位標示後合併起來。

head(data)

除了詞彙及詞頻以外,還多了一個type欄位來區分2/22以前或是以後。

plot_merge <- data %>% 
  group_by(type) %>% 
  top_n(15, sum) %>% 
  ungroup() %>% 
  mutate(word = reorder(word, sum)) %>%
  ggplot(aes(x=word, y=sum, fill = type)) +
  geom_col(show.legend = FALSE) +
  labs(x = NULL, y="詞頻") +
  facet_wrap(~type, ncol = 1, scales="free") + 
  coord_flip()+
  theme(text = element_text(family = "Heiti TC Light"))
plot_merge

由上圖可以看到,最常出現的字仍為「還願」、「遊戲」,然而在2/22以後「符咒」、「習近平」、「小熊維尼」等詞彙討論數異常升高。

plot_merge <- data %>% 
  group_by(type) %>% 
  top_n(25, sum) %>%
  ungroup() %>% 
  #filter(!(duplicated(word)| duplicated(word, fromLast = TRUE))) #%>% 
  group_by(word) %>%
  filter(n()==1) %>%
  ungroup() %>% 
  mutate(word = reorder(word, sum)) %>% 
  ggplot(aes(word, sum, fill = type)) +
  geom_col(show.legend = FALSE) +
  labs(x = NULL, y="總和") +
  facet_wrap(~type, ncol = 1, scales="free") + 
  coord_flip()+
  theme(text = element_text(family = "Heiti TC Light"))
plot_merge

將重複的詞彙移除後,可以看到在2/22以前討論的辭彙多是與遊戲劇情有有關的,例如「實況」、「返校」、「劇情」、「直播」、「老師」、「國產」等…
但是在2/22之後,常出現的詞彙變成,「符咒」、「習近平」、「小熊維尼」、「大陸」、「下架」等。

Ch.3 情緒折線圖

透過文字分析平台可以輸出每篇文章的情緒分數,在資料預覽頁面選擇「文章+情緒」選項即可得到資料。

載入資料

csv_sen <- fread("./data/social_media_0305_artSen.csv", encoding = "UTF-8")
head(csv_sen)

資料欄位介紹:
1. artTitle: 文章之標題,注意:不同文章可能會有完全相同的標題。
2. artDate: 文章發佈之日期。
3. artTime: 文章發佈之時間。
4. artUrl: 文章之網址,每篇文章之網址為獨一無二的,可用來辨識相同標題之不同文章。
5. positive_emotion_grade: 本篇文章出現多少次正面情緒詞彙。
6. negative_emotion_grade: 本篇文章出現多少次負面情緒詞彙。
7. neutral_emotion_grade: 本篇文章出現多少次中性情緒詞彙。

csv_sen$artDate = csv_sen$artDate %>% as.Date("%Y/%m/%d")
str(csv_sen)
Classes ‘data.table’ and 'data.frame':  683 obs. of  7 variables:
 $ artTitle              : chr  "[新聞]萌妹子香汗淋漓 彎腰向四面佛還願" "[問卦]還願會有櫻花妹玩嗎?" "[問卦]求神不還願會怎麼樣?" "[新聞]影/比《返校》更恐怖!赤燭新作《還願》" ...
 $ artDate               : Date, format: "2019-01-03" "2019-01-13" "2019-01-13" ...
 $ artTime               : chr  "14:50:55" "04:44:56" "05:41:36" "06:44:11" ...
 $ artUrl                : chr  "https://www.ptt.cc/bbs/Gossiping/M.1546556219.A.A7A.html" "https://www.ptt.cc/bbs/Gossiping/M.1547383858.A.D14.html" "https://www.ptt.cc/bbs/Gossiping/M.1547387269.A.3A7.html" "https://www.ptt.cc/bbs/Gossiping/M.1547391016.A.E55.html" ...
 $ positive_emotion_grade: int  4 1 0 2 4 1 1 0 16 7 ...
 $ negative_emotion_grade: int  0 0 2 1 3 1 0 1 9 2 ...
 $ neutral_emotion_grade : int  1 0 0 1 1 0 0 0 2 0 ...
 - attr(*, ".internal.selfref")=<externalptr> 

與Ch.1相同,資料預設日期格式為“chr”,我們需要先將其轉為“date”格式。

csv_sen <- csv_sen %>% filter(artDate >= "2019-02-15")

從前幾個章節得到主要討論都是由2/15後才開始,所以先把不相關的雜訊排除。

data_sen <- csv_sen %>% 
  group_by(artDate) %>% 
  summarise(positive = sum(positive_emotion_grade), negative = sum(negative_emotion_grade), neutral = sum(neutral_emotion_grade))
head(data_sen)

將資料按照日期分群後,加總每天的三種情緒分數,並進行標準化。

data_sen %>% ggplot(aes(x= artDate)) +
  geom_line(aes(y = positive, colour = "red")) +
  geom_line(aes(y = negative, colour = "blue")) +
  geom_line(aes(y = neutral, colour = "yellow")) + 
  scale_x_date(labels = date_format("%Y/%m/%d")) +
  scale_color_discrete(name="情緒種類", labels = c("positive","negative","neutral")) + 
  ggtitle("還願 討論情緒") + 
  xlab("日期") + 
  ylab("分數") + 
  theme(text = element_text(family = "Heiti TC Light"))

練習練習~

剛剛我們將資料按照日期分群後,加總計算了每天的三種情緒分數。 但是我們可以發現每天的文章數量造成辭彙的數量不一樣,所以情緒圖的走勢會有很大的起伏。 請各位將每日的情緒總值進行標準化後再畫一次情緒的走勢圖。

公式:
positive = positive / (positive + negative + neutral)
negative = negative / (positive + negative + neutral)
neutral = neutral / (positive + negative + neutral)

hint:剛剛summarise那邊可以順便計算每日的情緒詞總數
hint2:最後會用到mutate喔

### Code Here ###
LS0tCnRpdGxlOiAi5Lul5paH5a2X5YiG5p6Q5bmz5Y+w5LmL6LOH5paZ6YCy6KGM5Z+65pys57mq5ZyWIgphdXRob3I6ICJNaW5nTHVuIFd1IgpkYXRlOiAiMjAxOS8wMy8wNSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OiBkZWZhdWx0CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CmFic3RyYWN0OiAi5L2/55So5paH5a2X5YiG5p6Q5bmz5Y+w6Ly45Ye65LmL6LOH5paZ77yM5LulJ2RwbHlyJ+WPiidnZ3Bsb3QyJ+Wll+S7tumAsuihjOWfuuacrOizh+aWmeimluimuuWMliIKLS0tCiMgQ2guMCA6IOizh+aWmeWPluW+l+WPiuingOW/teikh+e/kgojIyAxLiDos4fmlpnlj5blvpflj4rlpZfku7bovInlhaUKPiDovInlhaXnmoTos4fmlpnmmK/nlLHkuK3lsbHlpKflrbjnrqHnkIblrbjpmaLmloflrZfliIbmnpDlubPlj7Dlj5blvpfvvIzlnKjlubPlj7Dos4fmlpnovLjlh7rljYDloYrpgbjmk4fjgIzmlofnq6Ar6Kme5b2ZK+ipnumgu+OAjemBuOmghe+8jOWNs+WPr+WPluW+l+ebuOWQjOagvOW8j+S5i2NzduaqlOahiOOAggoKIyMjIOizh+aWmeewoeS7iwo+IOacrOizh+aWmeeCujIwMTkvMDEvMDEgfiAyMDE5LzAzLzA0IFBUVOWFq+WNpueJiOS5i+izh+aWme+8jOmAj+mBjuaWh+Wtl+WIhuaekOW5s+WPsOaqoue0ouOAjOmChOmhmOOAjeOAgeOAjOe+juW/g+OAjeWFqeWAi+mXnOmNteWtl++8jOWFseaQnOWwi+WIsDY4M+evh+aWh+eroOOAggoKYGBge3J9ClN5cy5zZXRsb2NhbGUoY2F0ZWdvcnkgPSAiTENfQUxMIiwgbG9jYWxlID0gInpoX1RXLlVURi04IikgIyDpgb/lhY3kuK3mlofkuoLnorwKYGBgCgojIyDlronoo53pnIDopoHnmoRwYWNrYWdlcwpgYGB7cn0KcGFja2FnZXMgPSBjKCJkcGx5ciIsImdncGxvdDIiLCAiZGF0YS50YWJsZSIsICJzY2FsZXMiKQpleGlzdGluZyA9IGFzLmNoYXJhY3RlcihpbnN0YWxsZWQucGFja2FnZXMoKVssMV0pCmZvcihwa2cgaW4gcGFja2FnZXNbIShwYWNrYWdlcyAlaW4lIGV4aXN0aW5nKV0pIGluc3RhbGwucGFja2FnZXMocGtnKQpgYGAKCmBgYHtyfQpyZXF1aXJlKGdncGxvdDIpCnJlcXVpcmUoZHBseXIpCnJlcXVpcmUoZGF0YS50YWJsZSkKcmVxdWlyZShzY2FsZXMpCgpjc3YgPC0gZnJlYWQoIi4vZGF0YS9zb2NpYWxfbWVkaWFfMDMwNV9hcnRXb3JkRnJlcS5jc3YiLCBlbmNvZGluZyA9ICJVVEYtOCIpCmNzdiRhcnREYXRlID0gY3N2JGFydERhdGUgJT4lIGFzLkRhdGUoIiVZLyVtLyVkIikKc3RyKGNzdikKYGBgCj4g6LOH5paZ6aCQ6Kit5LmL5pel5pyf5qyE5L2N5pivImNociLmoLzlvI/vvIzlnKjnlavlnJbliY3miJHlgJHpoIjlhYjlsIflhbbovYnngroiZGF0ZSLmoLzlvI/vvIzovYnmj5vlvozlj6/ku6XpgI/pgY5zdHLmjIfku6Tkvobnorroqo3mrITkvY3lnovmhYvjgIIKCmBgYHtyfQpoZWFkKGNzdikKYGBgCgojIyMg6LOH5paZ5qyE5L2NCj4gMS4gYXJ0VGl0bGU6IOaWh+eroOS5i+aomemhjO+8jOazqOaEjzrkuI3lkIzmlofnq6Dlj6/og73mnIPmnInlrozlhajnm7jlkIznmoTmqJnpoYzjgIIKMi4gYXJ0RGF0ZTog5paH56ug55m85L2I5LmL5pel5pyf44CCCjMuIGFydFRpbWU6IOaWh+eroOeZvOS9iOS5i+aZgumWk+OAggo0LiBhcnRVcmw6IOaWh+eroOS5i+e2suWdgO+8jOavj+evh+aWh+eroOS5i+e2suWdgOeCuueNqOS4gOeEoeS6jOeahO+8jOWPr+eUqOS+hui+qOitmOebuOWQjOaomemhjOS5i+S4jeWQjOaWh+eroOOAggo1LiB3b3JkOiDoqZ7lvZnjgIIKNi4gY291bnQ6IOipnumgu+OAggoKIyBDaC4xIOaXpeacn+aKmOe3muWclgo+IOmAmeWAi+eroOevgOeahOebrueahOaYr+ioiOeul+WHuuavj+S4gOWkqeaWh+eroOeahOeZvOihqOaVuOmHj++8jOWPr+S7peeci+WHuueJueWumuS4u+mhjOiojuirlueahOeGseW6puOAggoKIyMg6LOH5paZ6JmV55CGCgpgYGB7cn0KZGF0YSA8LSBjc3YgJT4lIAogIHNlbGVjdChhcnREYXRlLCBhcnRVcmwpICU+JSAKICBkaXN0aW5jdCgpCmBgYAo+IOeUseaWvOmAmeS7veizh+aWmeeahOavj+S4gOWIl+aYr+eJueWumuaWh+eroOeahOavj+S4gOWAi+ipnuW9me+8jOaIkeWAkeWPqumcgOimgeaWh+eroOS7peWPiuaXpeacn+WFqeWAi+ashOS9jeWNs+WPr++8jOWFtuS7lumHjeikh+ashOS9jeWPr+S7peWOu+mZpOOAgijkuIDnr4fmlofnq6DmnInlvojlpJrlgIvoqZ7lvZnvvIzmiYDku6XmnIPmnInlvojlpJrliJfvvIzkvYbmiJHlgJHlj6rpnIDopoHkv53nlZnkuIDlgItVUkzljbPlj68p44CCCgoKYGBge3J9CmFydGljbGVfY291bnRfYnlfZGF0ZSA8LSBkYXRhICU+JSAKICBncm91cF9ieShhcnREYXRlKSAlPiUgCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKQoKaGVhZChhcnRpY2xlX2NvdW50X2J5X2RhdGUsIDIwKQpgYGAKPiDmjInnhafml6XmnJ/liIbnvqTvvIzoqIjnrpfmr4/lpKnlhbHmnInlub7nr4foqI7oq5bmlofnq6DjgIIKCmBgYHtyfQpwbG90X2RhdGUgPC0gCiAgIyBkYXRhCiAgYXJ0aWNsZV9jb3VudF9ieV9kYXRlICU+JSAKICAjIGFlc3RoZXRpY3MKICBnZ3Bsb3QoYWVzKHggPSBhcnREYXRlLCB5ID0gY291bnQpKSArCiAgIyBnZW9tZXRyaWNzCiAgZ2VvbV9saW5lKGNvbG9yID0gIiMwMEFGQkIiLCBzaXplID0gMikgKyAKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBhcy5udW1lcmljKGFzLkRhdGUoIjIwMTktMDItMTUiKSksIGNvbD0ncmVkJykgKyAKICAjIGNvb3JkaW5hdGVzCiAgc2NhbGVfeF9kYXRlKGxhYmVscyA9IGRhdGVfZm9ybWF0KCIlWS8lbS8lZCIpKSArCiAgZ2d0aXRsZSgi6YKE6aGYIOiojuirluaWh+eroOaVuCIpICsgCiAgeGxhYigi5pel5pyfIikgKyAKICB5bGFiKCLmlbjph48iKSArIAogICMgdGhlbWUKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJIZWl0aSBUQyBMaWdodCIpKSAj5Yqg5YWl5Lit5paH5a2X5Z6L6Kit5a6a77yM6YG/5YWN5Lit5paH5a2X6aGv56S66Yyv6Kqk44CCCgpwbG90X2RhdGUKYGBgCj4g5b6e5LiK5ZyW5Lit5Y+v5Lul55yL5Yiw6Zec5pa844CM6YKE6aGY44CN55qE6KiO6KuW5b6eMDIvMTXplovlp4vliqDmuqvvvIzlsI3nhac8YSBocmVmPSJodHRwczovL3poLndpa2lwZWRpYS5vcmcvd2lraS8lRTglQkYlOTglRTYlODQlQkZfKCVFNiVCOCVCOCVFNiU4OCU4RikiPue2reWfuueZvuenkeewoeS7izwvYT7lj6/lvpfnn6XmraTmrL7pgYrmiLLmlrwyMDE5LzAyLzE55q2j5byP5LiK5biC44CCCgojIENoLjIg5paH5a2X6ZuyCj4g5o6l5LiL5L6G5oiR5YCR5L6G5aSn55Wl6KeA5a+f6KiO6KuW55qE5YWn5a6554K65L2V77yM5L2/55So55qE5pa55byP54K65paH5a2X6Zuy44CCCgpgYGB7cn0KZGF0YSA8LSBjc3YgJT4lIAogIGdyb3VwX2J5KHdvcmQpICU+JSAKICBzdW1tYXJpc2Uoc3VtID0gc3VtKGNvdW50KSkgJT4lIAogIGFycmFuZ2UoZGVzYyhzdW0pKQpgYGAKPiDlhYjlsIfos4fmlpnpm4bkuK3miYDmnInmlofnq6DmjInnhafmloflrZfpgLLooYzliIbnvqTvvIzoqIjnrpfmr4/kuIDlgIvlrZfnmoTnuL3oqZ7poLvjgIIKCmBgYHtyfQpoZWFkKGRhdGEpCmBgYAo+IOe1kOaenOeCuue4veipnumgu+acgOWkmueahOWtl+OAggoKYGBge3J9CnJlcXVpcmUod29yZGNsb3VkMikKZGF0YSAlPiUgd29yZGNsb3VkMigpCmBgYAo+IOWwh+aVtOeQhuWlveeahOizh+aWmeebtOaOpemAgeWFpXdvcmRjbG91ZDIgZnVuY3Rpb27ljbPlj6/lvpfliLDmloflrZfpm7LjgILmraTlpZfku7bngrrkupLli5XlvI/ku4vpnaLvvIzkvb/nlKjmu5HpvKDnp7vli5XliLDnibnlrprnmoToqZ7lvZnvvIznlavpnaLmnIPlkIzmmYLpoa/npLroqZ7lvZnku6Xlj4rlsI3mh4nnmoToqZ7poLvjgIIKCiMjIOaMieeFp+aXpeacn+mAsuihjOWNgOWIhgo+IOWcqENoLjHkuK3nmoTml6XmnJ/mipjnt5rlnJbkuK3vvIzmiJHlgJHnmbznj77lnKgyLzIy5YmN5b6M6KiO6KuW5pW46YeP5pyJ5LiA5rOi6L2J5oqY77yM5oiR5YCR5L6G55yL55yLMi8yMuWJjeW+jOeahOiojuirluWFp+WuueaYr+WQpuacieS4jeWQjOeahOWcsOaWueOAggoKYGBge3J9CmRhdGFfYmVmb3JlIDwtIGNzdiAlPiUgZmlsdGVyKGFydERhdGUgPD0gIjIwMTktMDItMjIiKQpkYXRhX2FmdGVyIDwtIGNzdiAlPiUgZmlsdGVyKGFydERhdGUgPiAiMjAxOS0wMi0yMiIpCmBgYAo+IOaMieeFp+aXpeacn+WIh+WIhuizh+aWmQoKYGBge3J9CmRhdGFfYmVmb3JlIDwtIGRhdGFfYmVmb3JlICU+JSAKICBncm91cF9ieSh3b3JkKSAlPiUgCiAgc3VtbWFyaXNlKHN1bSA9IHN1bShjb3VudCkpICU+JSAKICBhcnJhbmdlKGRlc2Moc3VtKSkKCmRhdGFfYWZ0ZXIgPC0gZGF0YV9hZnRlciAlPiUgCiAgZ3JvdXBfYnkod29yZCkgJT4lIAogIHN1bW1hcmlzZShzdW0gPSBzdW0oY291bnQpKSAlPiUgCiAgYXJyYW5nZShkZXNjKHN1bSkpCmBgYAo+IOiIh0NoLjLnm7jlkIznmoTos4fmlpnomZXnkIbjgIIKCmBgYHtyfQpwbG90X2JlZm9yZSA8LSBkYXRhX2JlZm9yZSAlPiUgd29yZGNsb3VkMigpCnBsb3RfYmVmb3JlCmBgYAo+IDIvMjIg5LmL5YmN55qE5paH5a2X6ZuyCgpgYGB7cn0KcGxvdF9hZnRlciA8LSBkYXRhX2FmdGVyICU+JSB3b3JkY2xvdWQyKCkKcGxvdF9hZnRlcgpgYGAKPiAyLzIy5LmL5b6M55qE5paH5a2X6ZuyCgo+IOingOWvn+WFqeW8teWclueJh+WPr+S7peeZvOePvu+8jOWcqDIvMjLku6XliY3vvIzkuLvopoHoqI7oq5bngrrjgIzlr6bms4HjgI3jgIHjgIzliofmg4XjgI3jgIHjgIznvo7lv4PjgI3nrYnpgYrmiLLmnKzouqvlhaflrrnvvIzmiJbmmK/oiIfjgIzov5TmoKHjgI3nrYnnm7jpl5zkvZzlk4HkuYvmr5TovIPjgIIg5ZyoMi8yMuS7peW+jO+8jOmWi+Wni+WHuuePvuWkp+mHj+OAjOS4reWci+OAjeOAgeOAjOWPsOeBo+OAjeOAgeOAjOe/kui/keW5s+OAjeOAgeOAjOespuWSkuOAjeOAgeOAjOWwj+eGiue2reWwvOOAjeetiemXnOmNteWtl+OAguWwjeeFpzxhIGhyZWY9Imh0dHBzOi8vemgud2lraXBlZGlhLm9yZy93aWtpLyVFOCVCRiU5OCVFNiU4NCVCRl8oJUU2JUI4JUI4JUU2JTg4JThGKSI+57at5Z+655m+56eR57Ch5LuLPC9hPuWPr+W+l+efpeatpOasvumBiuaIsuaWvDIwMTkvMi8yMeeIhueZvOespuWSkueIreitsOS6i+S7tu+8jOWwjuiHtOiojuirlueEpum7numWi+Wni+aooeeziuOAggoKIyMg57e057+S57e057+S772eCj4g5Y+v5Lul55m854++5pys5qyh6LOH5paZ6ZuG5piv5L2/55So44CM5YqH5oOF44CN44CB44CM576O5b+D44CN54K66Zec6Y215a2X5oqT55qE77yM5omA5Lul55CG5omA55W254S244CM5YqH5oOF44CN6IiH44CM576O5b+D44CN55qE6Kme6aC75pyD5b6I6auY44CCPGJyPgrpgqPpurzlsLHoq4vlkITkvY3lsIfpgJnlhanlgIvpl5zpjbXlrZfnp7vpmaTlvozph43mlrDnuaroo73mloflrZfpm7LlkKfvvIEKCmBgYHtyfQojIyMgQ29kZSBIZXJlICMjIwoKYGBgCgoKIyBDaDMuIOmVt+aineWclgo+IOaWh+Wtl+mbsuWPr+S7peebtOimuueci+WHuui8g+W4uOaPkOWIsOeahOWtl++8jOS9huWmguaenOaDs+W+l+WIsOeyvueiuueahOOAjOacgOW4uOWHuuePvuipnuW9meOAje+8jOaIkeWAkeWJh+WPr+S7pemAj+mBjumVt+aineWcluS+huafpeeci+OAggoKYGBge3J9CmRhdGEgPC0gcmJpbmQoZGF0YV9iZWZvcmUgJT4lIG11dGF0ZSh0eXBlPSJiZWZvcmUiKSwgZGF0YV9hZnRlciAlPiUgbXV0YXRlKHR5cGU9ImFmdGVyIikpCmBgYAo+IOWwhzIvMjLliY3lvoznmoTos4fmlpnliqDlhaXkuIDlgIvmrITkvY3mqJnnpLrlvozlkIjkvbXotbfkvobjgIIKCmBgYHtyfQpoZWFkKGRhdGEpCmBgYAo+IOmZpOS6huipnuW9meWPiuipnumgu+S7peWklu+8jOmChOWkmuS6huS4gOWAi3R5cGXmrITkvY3kvobljYDliIYyLzIy5Lul5YmN5oiW5piv5Lul5b6M44CCCgpgYGB7cn0KcGxvdF9tZXJnZSA8LSBkYXRhICU+JSAKICBncm91cF9ieSh0eXBlKSAlPiUgCiAgdG9wX24oMTUsIHN1bSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIHN1bSkpICU+JQogIGdncGxvdChhZXMoeD13b3JkLCB5PXN1bSwgZmlsbCA9IHR5cGUpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsIHk9IuipnumguyIpICsKICBmYWNldF93cmFwKH50eXBlLCBuY29sID0gMSwgc2NhbGVzPSJmcmVlIikgKyAKICBjb29yZF9mbGlwKCkrCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiSGVpdGkgVEMgTGlnaHQiKSkKCnBsb3RfbWVyZ2UKYGBgCj4g55Sx5LiK5ZyW5Y+v5Lul55yL5Yiw77yM5pyA5bi45Ye654++55qE5a2X5LuN54K644CM6YKE6aGY44CN44CB44CM6YGK5oiy44CN77yM54S26ICM5ZyoMi8yMuS7peW+jOOAjOespuWSkuOAjeOAgeOAjOe/kui/keW5s+OAjeOAgeOAjOWwj+eGiue2reWwvOOAjeetieipnuW9meiojuirluaVuOeVsOW4uOWNh+mrmOOAggoKYGBge3J9CnBsb3RfbWVyZ2UgPC0gZGF0YSAlPiUgCiAgZ3JvdXBfYnkodHlwZSkgJT4lIAogIHRvcF9uKDI1LCBzdW0pICU+JQogIHVuZ3JvdXAoKSAlPiUgCiAgI2ZpbHRlcighKGR1cGxpY2F0ZWQod29yZCl8IGR1cGxpY2F0ZWQod29yZCwgZnJvbUxhc3QgPSBUUlVFKSkpICMlPiUgCiAgZ3JvdXBfYnkod29yZCkgJT4lCiAgZmlsdGVyKG4oKT09MSkgJT4lCiAgdW5ncm91cCgpICU+JSAKICBtdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgc3VtKSkgJT4lIAogIGdncGxvdChhZXMod29yZCwgc3VtLCBmaWxsID0gdHlwZSkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwgeT0i57i95ZKMIikgKwogIGZhY2V0X3dyYXAofnR5cGUsIG5jb2wgPSAxLCBzY2FsZXM9ImZyZWUiKSArIAogIGNvb3JkX2ZsaXAoKSsKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJIZWl0aSBUQyBMaWdodCIpKQoKcGxvdF9tZXJnZQpgYGAKPiDlsIfph43opIfnmoToqZ7lvZnnp7vpmaTlvozvvIzlj6/ku6XnnIvliLDlnKgyLzIy5Lul5YmN6KiO6KuW55qE6L6t5b2Z5aSa5piv6IiH6YGK5oiy5YqH5oOF5pyJ5pyJ6Zec55qE77yM5L6L5aaC44CM5a+m5rOB44CN44CB44CM6L+U5qCh44CN44CB44CM5YqH5oOF44CN44CB44CM55u05pKt44CN44CB44CM6ICB5bir44CN44CB44CM5ZyL55Si44CN562JLi4uPGJyPgrkvYbmmK/lnKgyLzIy5LmL5b6M77yM5bi45Ye654++55qE6Kme5b2Z6K6K5oiQ77yM44CM56ym5ZKS44CN44CB44CM57+S6L+R5bmz44CN44CB44CM5bCP54aK57at5bC844CN44CB44CM5aSn6Zm444CN44CB44CM5LiL5p6244CN562J44CCCgojIENoLjMg5oOF57eS5oqY57ea5ZyWCj4g6YCP6YGO5paH5a2X5YiG5p6Q5bmz5Y+w5Y+v5Lul6Ly45Ye65q+P56+H5paH56ug55qE5oOF57eS5YiG5pW477yM5Zyo6LOH5paZ6aCQ6Ka96aCB6Z2i6YG45pOH44CM5paH56ugK+aDhee3kuOAjemBuOmgheWNs+WPr+W+l+WIsOizh+aWmeOAggoKIyMg6LyJ5YWl6LOH5paZCmBgYHtyfQpjc3Zfc2VuIDwtIGZyZWFkKCIuL2RhdGEvc29jaWFsX21lZGlhXzAzMDVfYXJ0U2VuLmNzdiIsIGVuY29kaW5nID0gIlVURi04IikKaGVhZChjc3Zfc2VuKQpgYGAKPiDos4fmlpnmrITkvY3ku4vntLnvvJo8YnI+Cj4gMS4gYXJ0VGl0bGU6IOaWh+eroOS5i+aomemhjO+8jOazqOaEjzrkuI3lkIzmlofnq6Dlj6/og73mnIPmnInlrozlhajnm7jlkIznmoTmqJnpoYzjgII8YnI+Cj4gMi4gYXJ0RGF0ZTog5paH56ug55m85L2I5LmL5pel5pyf44CCPGJyPgo+IDMuIGFydFRpbWU6IOaWh+eroOeZvOS9iOS5i+aZgumWk+OAgjxicj4KPiA0LiBhcnRVcmw6IOaWh+eroOS5i+e2suWdgO+8jOavj+evh+aWh+eroOS5i+e2suWdgOeCuueNqOS4gOeEoeS6jOeahO+8jOWPr+eUqOS+hui+qOitmOebuOWQjOaomemhjOS5i+S4jeWQjOaWh+eroOOAgjxicj4KPiA1LiBwb3NpdGl2ZV9lbW90aW9uX2dyYWRlOiDmnKznr4fmlofnq6Dlh7rnj77lpJrlsJHmrKHmraPpnaLmg4Xnt5LoqZ7lvZnjgII8YnI+Cj4gNi4gbmVnYXRpdmVfZW1vdGlvbl9ncmFkZTog5pys56+H5paH56ug5Ye654++5aSa5bCR5qyh6LKg6Z2i5oOF57eS6Kme5b2Z44CCPGJyPgo+IDcuIG5ldXRyYWxfZW1vdGlvbl9ncmFkZTog5pys56+H5paH56ug5Ye654++5aSa5bCR5qyh5Lit5oCn5oOF57eS6Kme5b2Z44CCCgpgYGB7cn0KY3N2X3NlbiRhcnREYXRlID0gY3N2X3NlbiRhcnREYXRlICU+JSBhcy5EYXRlKCIlWS8lbS8lZCIpCnN0cihjc3Zfc2VuKQpgYGAKPiDoiIdDaC4x55u45ZCM77yM6LOH5paZ6aCQ6Kit5pel5pyf5qC85byP54K6ImNociLvvIzmiJHlgJHpnIDopoHlhYjlsIflhbbovYnngroiZGF0ZSLmoLzlvI/jgIIKCmBgYHtyfQpjc3Zfc2VuIDwtIGNzdl9zZW4gJT4lIGZpbHRlcihhcnREYXRlID49ICIyMDE5LTAyLTE1IikKYGBgCj4g5b6e5YmN5bm+5YCL56ug56+A5b6X5Yiw5Li76KaB6KiO6KuW6YO95piv55SxMi8xNeW+jOaJjemWi+Wni++8jOaJgOS7peWFiOaKiuS4jeebuOmXnOeahOmbnOioiuaOkumZpOOAggoKYGBge3J9CmRhdGFfc2VuIDwtIGNzdl9zZW4gJT4lIAogIGdyb3VwX2J5KGFydERhdGUpICU+JSAKICBzdW1tYXJpc2UocG9zaXRpdmUgPSBzdW0ocG9zaXRpdmVfZW1vdGlvbl9ncmFkZSksIG5lZ2F0aXZlID0gc3VtKG5lZ2F0aXZlX2Vtb3Rpb25fZ3JhZGUpLCBuZXV0cmFsID0gc3VtKG5ldXRyYWxfZW1vdGlvbl9ncmFkZSkpCgpoZWFkKGRhdGFfc2VuKQpgYGAKPiDlsIfos4fmlpnmjInnhafml6XmnJ/liIbnvqTlvozvvIzliqDnuL3mr4/lpKnnmoTkuInnqK7mg4Xnt5LliIbmlbjvvIzkuKbpgLLooYzmqJnmupbljJbjgIIKCmBgYHtyfQpkYXRhX3NlbiAlPiUgZ2dwbG90KGFlcyh4PSBhcnREYXRlKSkgKwogIGdlb21fbGluZShhZXMoeSA9IHBvc2l0aXZlLCBjb2xvdXIgPSAicmVkIikpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBuZWdhdGl2ZSwgY29sb3VyID0gImJsdWUiKSkgKwogIGdlb21fbGluZShhZXMoeSA9IG5ldXRyYWwsIGNvbG91ciA9ICJ5ZWxsb3ciKSkgKyAKICBzY2FsZV94X2RhdGUobGFiZWxzID0gZGF0ZV9mb3JtYXQoIiVZLyVtLyVkIikpICsKICBzY2FsZV9jb2xvcl9kaXNjcmV0ZShuYW1lPSLmg4Xnt5LnqK7poZ4iLCBsYWJlbHMgPSBjKCJwb3NpdGl2ZSIsIm5lZ2F0aXZlIiwibmV1dHJhbCIpKSArIAogIGdndGl0bGUoIumChOmhmCDoqI7oq5bmg4Xnt5IiKSArIAogIHhsYWIoIuaXpeacnyIpICsgCiAgeWxhYigi5YiG5pW4IikgKyAKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJIZWl0aSBUQyBMaWdodCIpKQpgYGAKCgojIyDnt7Tnv5Lnt7Tnv5LvvZ4KPiDliZvliZvmiJHlgJHlsIfos4fmlpnmjInnhafml6XmnJ/liIbnvqTlvozvvIzliqDnuL3oqIjnrpfkuobmr4/lpKnnmoTkuInnqK7mg4Xnt5LliIbmlbjjgIIK5L2G5piv5oiR5YCR5Y+v5Lul55m854++5q+P5aSp55qE5paH56ug5pW46YeP6YCg5oiQ6L6t5b2Z55qE5pW46YeP5LiN5LiA5qij77yM5omA5Lul5oOF57eS5ZyW55qE6LWw5Yui5pyD5pyJ5b6I5aSn55qE6LW35LyP44CCCuiri+WQhOS9jeWwh+avj+aXpeeahOaDhee3kue4veWAvOmAsuihjOaomea6luWMluW+jOWGjeeVq+S4gOasoeaDhee3kueahOi1sOWLouWcluOAgjxicj48YnI+CuWFrOW8j++8mjxicj4KcG9zaXRpdmUgPSBwb3NpdGl2ZSAvIChwb3NpdGl2ZSArIG5lZ2F0aXZlICsgbmV1dHJhbCk8YnI+Cm5lZ2F0aXZlID0gbmVnYXRpdmUgLyAocG9zaXRpdmUgKyBuZWdhdGl2ZSArIG5ldXRyYWwpPGJyPgpuZXV0cmFsID0gbmV1dHJhbCAvIChwb3NpdGl2ZSArIG5lZ2F0aXZlICsgbmV1dHJhbCk8YnI+PGJyPgpoaW5077ya5Ymb5Ymbc3VtbWFyaXNl6YKj6YKK5Y+v5Lul6aCG5L6/6KiI566X5q+P5pel55qE5oOF57eS6Kme57i95pW4PGJyPgpoaW50Mu+8muacgOW+jOacg+eUqOWIsG11dGF0ZeWWlAoKYGBge3J9CiMjIyBDb2RlIEhlcmUgIyMjCgpgYGAKCgoKCg==