使用文字分析平台輸出之資料,以’dplyr’及’ggplot2’套件進行基本資料視覺化

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

1. 資料取得及套件載入

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

資料簡介

本資料內容為將 Dcard 股票看板的文章,自 2021/01/01 到 2021/03/08 為止,透過文字分析平台整理,共得到 1415 篇文章。

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

安裝需要的packages

packages = c("dplyr","ggplot2", "data.table", "scales", "tidytext")
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)

載入需要的packages以及資料

require(dplyr)
require(ggplot2)
require(data.table)
require(scales)
require(wordcloud2)
require(tidytext)

載入自平台下載下來的資料

csv <- fread("./data/dcard_stock_artWordFreq.csv", encoding = "UTF-8")
csv$artDate <- csv$artDate %>% as.Date("%Y/%m/%d")
str(csv)
Classes ‘data.table’ and 'data.frame':  77620 obs. of  6 variables:
 $ artTitle: chr  "#分享 成交量-看書學習進場竟慘" "#分享 成交量-看書學習進場竟慘" "#分享 成交量-看書學習進場竟慘" "#分享 成交量-看書學習進場竟慘" ...
 $ artDate : Date, format: "2021-01-01" "2021-01-01" "2021-01-01" "2021-01-01" ...
 $ artTime : chr  "11:17:30" "11:17:30" "11:17:30" "11:17:30" ...
 $ artUrl  : chr  "https://www.dcard.tw/f/stock/p/235090575" "https://www.dcard.tw/f/stock/p/235090575" "https://www.dcard.tw/f/stock/p/235090575" "https://www.dcard.tw/f/stock/p/235090575" ...
 $ word    : chr  "股票" "股市" "________________________________________" "一檔" ...
 $ count   : int  6 5 5 5 4 3 3 3 3 3 ...
 - attr(*, ".internal.selfref")=<externalptr> 

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

檢視前6筆資料

head(csv)

資料欄位

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

Ch.1 日期折線圖

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

資料處理

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

select(): 我們只需要文章以及日期兩個欄位即可,其他欄位不需要。
distinct(): 一篇文章有很多個詞彙,所以會有很多列,但我們只需要一篇文章保留一個列即可。

article_count_by_date <- data %>% 
  group_by(artDate) %>% 
  summarise(count = n())
`summarise()` ungrouping output (override with `.groups` argument)
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 = 1) + 
  # 2021-01-20 紅線
  geom_vline(xintercept = as.numeric(as.Date("2021-01-20")), col='red', size = 1) + 
  # 2021-02-23 紅線
  geom_vline(xintercept = as.numeric(as.Date("2021-02-23")), col='red', size = 1) + 
  # coordinates
  scale_x_date(labels = date_format("%Y/%m/%d")) +
  ggtitle("Dcard 股票看板 討論文章數") + 
  xlab("日期") + 
  ylab("數量") + 
  # theme
  theme(text = element_text(family = "Heiti TC Light")) #加入中文字型設定,避免中文字顯示錯誤。

plot_date

從上圖中可以看到在 Dcard 中「股票看板」的討論數量時高時低,我們將兩個特別高峰標示出來。

Ch.2 文字雲

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

data <- csv %>% 
  group_by(word) %>% 
  summarise(sum = sum(count), .groups = 'drop') %>% 
  arrange(desc(sum))

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

head(data)

結果為總詞頻最多的字。

data %>% filter(sum > 50) %>% wordcloud2()

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

按照日期進行區分

在Ch.1中的日期折線圖中,我們有標示出特別高的兩天(01/20與02/23),這裡我們透過文字雲來看兩天內容是否有差。

data_0120 <- csv %>% filter(artDate == "2021-01-20")
data_0223 <- csv %>% filter(artDate == "2021-02-23")

按照日期切出資料

data_0120 <- data_0120 %>% 
  group_by(word) %>% 
  summarise(sum = sum(count), .groups = 'drop') %>% 
  arrange(desc(sum))

data_0223 <- data_0223 %>% 
  group_by(word) %>% 
  summarise(sum = sum(count), .groups = 'drop') %>% 
  arrange(desc(sum))

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

plot_0120 <- data_0120 %>% wordcloud2()
# plot_0120

01/20 的文字雲(html中會顯示不出來第一張以後的文字雲,請將程式碼下載下來即可成功繪出)

plot_0223 <- data_0223 %>% wordcloud2()
# plot_0223

02/23 的文字雲(html中會顯示不出來第一張以後的文字雲,請將程式碼下載下來即可成功繪出文字雲)

觀察兩張圖片中出現的詞可以發現,01/20 有出現斗大的「停損」、「跌破」等字。 02/23 則出現「向上」、「進場」、「補量」等字。與當天的台股方向一致。

練習練習~

可以發現本次資料集內有許多日常用語,包括今天、今日、目前、大家等。
為了讓我們看的更清楚,請各位同學將一些日常用語移除後重新繪製文字雲吧!

### Code Here ###

Ch3. 長條圖

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

data <- rbind(data_0120 %>% mutate(date="01/20"), data_0223 %>% mutate(date="02/23"))

將 01/20 與 02/23 兩日的資料加入一個欄位標示後合併起來。

head(data)

除了詞彙及詞頻以外,還多了一個date欄位來區分日期。

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

plot_merge

由上圖可以看出,有許多不同之處,但也有一些重複出現的字彙。

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

plot_merge

將重複的詞彙移除後,可以更清楚看到在 01/20 與 02/23 的討論差異。

Ch.4 情緒折線圖

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

載入資料

csv_sen <- fread("./data/dcard_stock_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':  1415 obs. of  7 variables:
 $ artTitle              : chr  "#分享 成交量-看書學習進場竟慘" "榮運" "#其他 2020股市最慘的賠多少?" "大家有慣用的資訊獲取軟體嗎?" ...
 $ artDate               : Date, format: "2021-01-01" "2021-01-01" "2021-01-01" "2021-01-01" ...
 $ artTime               : chr  "11:17:30" "15:30:05" "15:49:04" "16:50:49" ...
 $ artUrl                : chr  "https://www.dcard.tw/f/stock/p/235090575" "https://www.dcard.tw/f/stock/p/235092009" "https://www.dcard.tw/f/stock/p/235092121" "https://www.dcard.tw/f/stock/p/235092427" ...
 $ positive_emotion_grade: int  10 1 1 0 1 8 0 2 0 21 ...
 $ negative_emotion_grade: int  7 1 1 2 0 3 0 1 0 1 ...
 $ neutral_emotion_grade : int  1 0 0 1 0 1 0 0 1 0 ...
 - attr(*, ".internal.selfref")=<externalptr> 

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

data_sen <- csv_sen %>% 
  group_by(artDate) %>% 
  summarise(positive = sum(positive_emotion_grade), negative = sum(negative_emotion_grade), neutral = sum(neutral_emotion_grade))
`summarise()` ungrouping output (override with `.groups` argument)
head(data_sen)

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

data_sen %>% ggplot(aes(x= artDate)) +
  geom_line(aes(y = positive, col = "positive")) +
  geom_line(aes(y = negative, col = "negative")) +
  geom_line(aes(y = neutral, col = "neutral")) + 
  scale_x_date(labels = date_format("%Y/%m/%d")) +
  #scale_color_discrete(name="情緒種類", labels = c("positive","negative","neutral")) + 
  ggtitle("Dcard 股票看板 討論情緒") + 
  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 ###

Homework

以讀書會為單位,使用文字分析平台,針對有興趣的議題爬取資料,資料來源不限

接著使用平台預覽中的圖形試做的功能,挑出三張有特色的圖片,擷取畫面後貼到word,並在每張圖片皆附上說明

作業轉成PDF格式,上傳至網大「第三週HW」,每組一人上傳即可。

LS0tCnRpdGxlOiAi5Lul5paH5a2X5YiG5p6Q5bmz5Y+w542y5Y+W5LmL6LOH5paZ6YCy6KGM5Z+65pys57mq5ZyW5YiG5p6QIgphdXRob3I6ICJLdW4tSHNpYW5nIENoZW4iCmRhdGU6ICIyMDIxLzAzLzA1IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgaGlnaGxpZ2h0OiBweWdtZW50cwogICAgdGhlbWU6IGZsYXRseQogICAgY3NzOiBzdHlsZS5jc3MKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAotLS0KPiDkvb/nlKjmloflrZfliIbmnpDlubPlj7DovLjlh7rkuYvos4fmlpnvvIzku6UnZHBseXIn5Y+KJ2dncGxvdDIn5aWX5Lu26YCy6KGM5Z+65pys6LOH5paZ6KaW6Ka65YyWCgojIENoLjAgOiDos4fmlpnlj5blvpflj4rop4Dlv7XopIfnv5IKIyMgMS4g6LOH5paZ5Y+W5b6X5Y+K5aWX5Lu26LyJ5YWlCj4g6LyJ5YWl55qE6LOH5paZ5piv55Sx5Lit5bGx5aSn5a24566h55CG5a246Zmi5paH5a2X5YiG5p6Q5bmz5Y+w5Y+W5b6X77yM5Zyo5bmz5Y+w6LOH5paZ6Ly45Ye65Y2A5aGK6YG45pOH44CM5paH56ugK+ipnuW9mSvoqZ7poLvjgI3pgbjpoIXvvIzljbPlj6/lj5blvpfnm7jlkIzmoLzlvI/kuYtjc3bmqpTmoYjjgIIKCiMjIyDos4fmlpnnsKHku4sKPiDmnKzos4fmlpnlhaflrrnngrrlsIcgRGNhcmQg6IKh56Wo55yL5p2/55qE5paH56ug77yM6IeqIDIwMjEvMDEvMDEg5YiwIDIwMjEvMDMvMDgg54K65q2i77yM6YCP6YGO5paH5a2X5YiG5p6Q5bmz5Y+w5pW055CG77yM5YWx5b6X5YiwIDE0MTUg56+H5paH56ug44CCCgpgYGB7ciBlY2hvID0gVCwgcmVzdWx0cyA9ICdoaWRlJ30KU3lzLnNldGxvY2FsZShjYXRlZ29yeSA9ICJMQ19BTEwiLCBsb2NhbGUgPSAiemhfVFcuVVRGLTgiKSAjIOmBv+WFjeS4reaWh+S6gueivApgYGAKCiMjIyDlronoo53pnIDopoHnmoRwYWNrYWdlcwpgYGB7cn0KcGFja2FnZXMgPSBjKCJkcGx5ciIsImdncGxvdDIiLCAiZGF0YS50YWJsZSIsICJzY2FsZXMiLCAidGlkeXRleHQiKQpleGlzdGluZyA9IGFzLmNoYXJhY3RlcihpbnN0YWxsZWQucGFja2FnZXMoKVssMV0pCmZvcihwa2cgaW4gcGFja2FnZXNbIShwYWNrYWdlcyAlaW4lIGV4aXN0aW5nKV0pIGluc3RhbGwucGFja2FnZXMocGtnKQpgYGAKCiMjIyDovInlhaXpnIDopoHnmoRwYWNrYWdlc+S7peWPiuizh+aWmQpgYGB7ciBlY2hvID0gVCwgcmVzdWx0cyA9ICdoaWRlJ30KcmVxdWlyZShkcGx5cikKcmVxdWlyZShnZ3Bsb3QyKQpyZXF1aXJlKGRhdGEudGFibGUpCnJlcXVpcmUoc2NhbGVzKQpyZXF1aXJlKHdvcmRjbG91ZDIpCnJlcXVpcmUodGlkeXRleHQpCmBgYAoKIyMjIOi8ieWFpeiHquW5s+WPsOS4i+i8ieS4i+S+hueahOizh+aWmQpgYGB7cn0KY3N2IDwtIGZyZWFkKCIuL2RhdGEvZGNhcmRfc3RvY2tfYXJ0V29yZEZyZXEuY3N2IiwgZW5jb2RpbmcgPSAiVVRGLTgiKQpjc3YkYXJ0RGF0ZSA8LSBjc3YkYXJ0RGF0ZSAlPiUgYXMuRGF0ZSgiJVkvJW0vJWQiKQpzdHIoY3N2KQpgYGAKPiDos4fmlpnpoJDoqK3kuYvml6XmnJ/mrITkvY3mmK8iY2hyIuagvOW8j++8jOWcqOeVq+WcluWJjeaIkeWAkemgiOWFiOWwh+WFtui9ieeCuiJkYXRlIuagvOW8j++8jOi9ieaPm+W+jOWPr+S7pemAj+mBjnN0cuaMh+S7pOS+hueiuuiqjeWQhOashOS9jeWei+aFi+OAggoKIyMjIOaqouimluWJjTbnrYbos4fmlpkKYGBge3J9CmhlYWQoY3N2KQpgYGAKCiMjIyDos4fmlpnmrITkvY0KPiAxLiBhcnRUaXRsZTog5paH56ug5LmL5qiZ6aGM77yM6aCI5rOo5oSP5LiN5ZCM5paH56ug5Y+v6IO95pyD5pyJ5a6M5YWo55u45ZCM55qE5qiZ6aGM44CCCjIuIGFydERhdGU6IOaWh+eroOeZvOS9iOS5i+aXpeacn+OAggozLiBhcnRUaW1lOiDmlofnq6DnmbzkvYjkuYvmmYLplpPjgIIKNC4gYXJ0VXJsOiDmlofnq6DkuYvntrLlnYDvvIzmr4/nr4fmlofnq6DkuYvntrLlnYDngrrnjajkuIDnhKHkuoznmoTvvIzlj6/nlKjkvobovqjorZjnm7jlkIzmqJnpoYzkuYvkuI3lkIzmlofnq6DjgIIKNS4gd29yZDog6Kme5b2Z44CCCjYuIGNvdW50OiDoqZ7poLvjgIIKCiMgQ2guMSDml6XmnJ/mipjnt5rlnJYKPiDpgJnlgIvnq6Dnr4DnmoTnm67nmoTmmK/oqIjnrpflh7rmr4/kuIDlpKnmlofnq6DnmoTnmbzooajmlbjph4/vvIzlj6/ku6XnnIvlh7rnibnlrprkuLvpoYzoqI7oq5bnmoTnhrHluqbjgIIKCiMjIOizh+aWmeiZleeQhgoKYGBge3J9CmRhdGEgPC0gY3N2ICU+JSAKICBkcGx5cjo6c2VsZWN0KGFydERhdGUsIGFydFVybCkgJT4lIAogIGRpc3RpbmN0KCkKYGBgCj4gc2VsZWN0KCk6IOaIkeWAkeWPqumcgOimgeaWh+eroOS7peWPiuaXpeacn+WFqeWAi+ashOS9jeWNs+WPr++8jOWFtuS7luashOS9jeS4jemcgOimgeOAgjxicj4KPiBkaXN0aW5jdCgpOiDkuIDnr4fmlofnq6DmnInlvojlpJrlgIvoqZ7lvZnvvIzmiYDku6XmnIPmnInlvojlpJrliJfvvIzkvYbmiJHlgJHlj6rpnIDopoHkuIDnr4fmlofnq6Dkv53nlZnkuIDlgIvliJfljbPlj6/jgIIKCgpgYGB7cn0KYXJ0aWNsZV9jb3VudF9ieV9kYXRlIDwtIGRhdGEgJT4lIAogIGdyb3VwX2J5KGFydERhdGUpICU+JSAKICBzdW1tYXJpc2UoY291bnQgPSBuKCkpCgpoZWFkKGFydGljbGVfY291bnRfYnlfZGF0ZSwgMjApCmBgYAo+IOaMieeFp+aXpeacn+WIhue+pO+8jOioiOeul+avj+WkqeWFseacieW5vuevh+iojuirluaWh+eroOOAggoKYGBge3J9CnBsb3RfZGF0ZSA8LSAKICAjIGRhdGEKICBhcnRpY2xlX2NvdW50X2J5X2RhdGUgJT4lIAogICMgYWVzdGhldGljcwogIGdncGxvdChhZXMoeCA9IGFydERhdGUsIHkgPSBjb3VudCkpICsKICAjIGdlb21ldHJpY3MKICBnZW9tX2xpbmUoY29sb3IgPSAiIzAwQUZCQiIsIHNpemUgPSAxKSArIAogICMgMjAyMS0wMS0yMCDntIXnt5oKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBhcy5udW1lcmljKGFzLkRhdGUoIjIwMjEtMDEtMjAiKSksIGNvbD0ncmVkJywgc2l6ZSA9IDEpICsgCiAgIyAyMDIxLTAyLTIzIOe0hee3mgogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGFzLm51bWVyaWMoYXMuRGF0ZSgiMjAyMS0wMi0yMyIpKSwgY29sPSdyZWQnLCBzaXplID0gMSkgKyAKICAjIGNvb3JkaW5hdGVzCiAgc2NhbGVfeF9kYXRlKGxhYmVscyA9IGRhdGVfZm9ybWF0KCIlWS8lbS8lZCIpKSArCiAgZ2d0aXRsZSgiRGNhcmQg6IKh56Wo55yL5p2/IOiojuirluaWh+eroOaVuCIpICsgCiAgeGxhYigi5pel5pyfIikgKyAKICB5bGFiKCLmlbjph48iKSArIAogICMgdGhlbWUKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJIZWl0aSBUQyBMaWdodCIpKSAj5Yqg5YWl5Lit5paH5a2X5Z6L6Kit5a6a77yM6YG/5YWN5Lit5paH5a2X6aGv56S66Yyv6Kqk44CCCgpwbG90X2RhdGUKYGBgCj4g5b6e5LiK5ZyW5Lit5Y+v5Lul55yL5Yiw5ZyoIERjYXJkIOS4reOAjOiCoeelqOeci+adv+OAjeeahOiojuirluaVuOmHj+aZgumrmOaZguS9ju+8jOaIkeWAkeWwh+WFqeWAi+eJueWIpemrmOWzsOaomeekuuWHuuS+huOAggoKIyBDaC4yIOaWh+Wtl+mbsgo+IOaOpeS4i+S+huaIkeWAkeS+huWkp+eVpeingOWvn+iojuirlueahOWFp+WuueeCuuS9le+8jOS9v+eUqOeahOaWueW8j+eCuuaWh+Wtl+mbsuOAggoKYGBge3J9CmRhdGEgPC0gY3N2ICU+JSAKICBncm91cF9ieSh3b3JkKSAlPiUgCiAgc3VtbWFyaXNlKHN1bSA9IHN1bShjb3VudCksIC5ncm91cHMgPSAnZHJvcCcpICU+JSAKICBhcnJhbmdlKGRlc2Moc3VtKSkKYGBgCj4g5YWI5bCH6LOH5paZ6ZuG5Lit5omA5pyJ5paH56ug5oyJ54Wn5paH5a2X6YCy6KGM5YiG576k77yM6KiI566X5q+P5LiA5YCL5a2X55qE57i96Kme6aC744CCCgpgYGB7cn0KaGVhZChkYXRhKQpgYGAKPiDntZDmnpzngrrnuL3oqZ7poLvmnIDlpJrnmoTlrZfjgIIKCmBgYHtyfQpkYXRhICU+JSBmaWx0ZXIoc3VtID4gNTApICU+JSB3b3JkY2xvdWQyKCkKYGBgCj4g5bCH5pW055CG5aW955qE6LOH5paZ55u05o6l6YCB5YWlIHdvcmRjbG91ZDIgZnVuY3Rpb24g5Y2z5Y+v5b6X5Yiw5paH5a2X6Zuy44CC5q2k5aWX5Lu254K65LqS5YuV5byP5LuL6Z2i77yM5L2/55So5ruR6byg56e75YuV5Yiw54m55a6a55qE6Kme5b2Z77yM55Wr6Z2i5pyD5ZCM5pmC6aGv56S66Kme5b2Z5Lul5Y+K5bCN5oeJ55qE6Kme6aC744CCCgojIyDmjInnhafml6XmnJ/pgLLooYzljYDliIYKPiDlnKhDaC4x5Lit55qE5pel5pyf5oqY57ea5ZyW5Lit77yM5oiR5YCR5pyJ5qiZ56S65Ye654m55Yil6auY55qE5YWp5aSpKDAxLzIw6IiHMDIvMjMp77yM6YCZ6KOh5oiR5YCR6YCP6YGO5paH5a2X6Zuy5L6G55yL5YWp5aSp5YWn5a655piv5ZCm5pyJ5beu44CCCgpgYGB7cn0KZGF0YV8wMTIwIDwtIGNzdiAlPiUgZmlsdGVyKGFydERhdGUgPT0gIjIwMjEtMDEtMjAiKQpkYXRhXzAyMjMgPC0gY3N2ICU+JSBmaWx0ZXIoYXJ0RGF0ZSA9PSAiMjAyMS0wMi0yMyIpCmBgYAo+IOaMieeFp+aXpeacn+WIh+WHuuizh+aWmQoKYGBge3J9CmRhdGFfMDEyMCA8LSBkYXRhXzAxMjAgJT4lIAogIGdyb3VwX2J5KHdvcmQpICU+JSAKICBzdW1tYXJpc2Uoc3VtID0gc3VtKGNvdW50KSwgLmdyb3VwcyA9ICdkcm9wJykgJT4lIAogIGFycmFuZ2UoZGVzYyhzdW0pKQoKZGF0YV8wMjIzIDwtIGRhdGFfMDIyMyAlPiUgCiAgZ3JvdXBfYnkod29yZCkgJT4lIAogIHN1bW1hcmlzZShzdW0gPSBzdW0oY291bnQpLCAuZ3JvdXBzID0gJ2Ryb3AnKSAlPiUgCiAgYXJyYW5nZShkZXNjKHN1bSkpCmBgYAo+IOiIh0NoLjLnm7jlkIznmoTos4fmlpnomZXnkIbjgIIKCmBgYHtyfQpwbG90XzAxMjAgPC0gZGF0YV8wMTIwICU+JSB3b3JkY2xvdWQyKCkKIyBwbG90XzAxMjAKYGBgCiFbXSh3b3JkY2xvdWRfMS5wbmcpCgo+IDAxLzIwIOeahOaWh+Wtl+mbsihodG1s5Lit5pyD6aGv56S65LiN5Ye65L6G56ys5LiA5by15Lul5b6M55qE5paH5a2X6Zuy77yM6KuL5bCH56iL5byP56K85LiL6LyJ5LiL5L6G5Y2z5Y+v5oiQ5Yqf57mq5Ye6KQoKYGBge3J9CnBsb3RfMDIyMyA8LSBkYXRhXzAyMjMgJT4lIHdvcmRjbG91ZDIoKQojIHBsb3RfMDIyMwpgYGAKIVtdKHdvcmRjbG91ZF8yLnBuZykKCj4gMDIvMjMg55qE5paH5a2X6ZuyKGh0bWzkuK3mnIPpoa/npLrkuI3lh7rkvobnrKzkuIDlvLXku6XlvoznmoTmloflrZfpm7LvvIzoq4vlsIfnqIvlvI/norzkuIvovInkuIvkvobljbPlj6/miJDlip/nuarlh7rmloflrZfpm7IpCgo+IOingOWvn+WFqeW8teWclueJh+S4reWHuuePvueahOipnuWPr+S7peeZvOePvu+8jDAxLzIwIOacieWHuuePvuaWl+Wkp+eahOOAjOWBnOaQjeOAjeOAgeOAjOi3jOegtOOAjeetieWtl+OAgiAKMDIvMjMg5YmH5Ye654++44CM5ZCR5LiK44CN44CB44CM6YCy5aC044CN44CB44CM6KOc6YeP44CN562J5a2X44CC6IiH55W25aSp55qE5Y+w6IKh5pa55ZCR5LiA6Ie044CCCgojIyDnt7Tnv5Lnt7Tnv5LvvZ4KPiDlj6/ku6Xnmbznj77mnKzmrKHos4fmlpnpm4blhafmnInoqLHlpJrml6XluLjnlKjoqp7vvIzljIXmi6zku4rlpKnjgIHku4rml6XjgIHnm67liY3jgIHlpKflrrbnrYnjgII8YnI+CueCuuS6huiuk+aIkeWAkeeci+eahOabtOa4healmu+8jOiri+WQhOS9jeWQjOWtuOWwh+S4gOS6m+aXpeW4uOeUqOiqnuenu+mZpOW+jOmHjeaWsOe5quijveaWh+Wtl+mbsuWQp++8gTxicj4KCmBgYHtyfQojIyMgQ29kZSBIZXJlICMjIwpgYGAKCiMgQ2gzLiDplbfmop3lnJYKPiDmloflrZfpm7Llj6/ku6Xnm7ToprrnnIvlh7rovIPluLjmj5DliLDnmoTlrZfvvIzkvYblpoLmnpzmg7PlvpfliLDnsr7norrnmoTjgIzmnIDluLjlh7rnj77oqZ7lvZnjgI3vvIzmiJHlgJHliYflj6/ku6XpgI/pgY7plbfmop3lnJbkvobmn6XnnIvjgIIKCmBgYHtyfQpkYXRhIDwtIHJiaW5kKGRhdGFfMDEyMCAlPiUgbXV0YXRlKGRhdGU9IjAxLzIwIiksIGRhdGFfMDIyMyAlPiUgbXV0YXRlKGRhdGU9IjAyLzIzIikpCmBgYAo+IOWwhyAwMS8yMCDoiIcgMDIvMjMg5YWp5pel55qE6LOH5paZ5Yqg5YWl5LiA5YCL5qyE5L2N5qiZ56S65b6M5ZCI5L216LW35L6G44CCCgpgYGB7cn0KaGVhZChkYXRhKQpgYGAKPiDpmaTkuoboqZ7lvZnlj4roqZ7poLvku6XlpJbvvIzpgoTlpJrkuobkuIDlgItkYXRl5qyE5L2N5L6G5Y2A5YiG5pel5pyf44CCCgpgYGB7cn0KcGxvdF9tZXJnZSA8LSBkYXRhICU+JSAKICBncm91cF9ieShkYXRlKSAlPiUgCiAgdG9wX24oMTAsIHN1bSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgbXV0YXRlKGRhdGUgPSBhcy5mYWN0b3IoZGF0ZSksCiAgICAgICAgIHdvcmQgPSByZW9yZGVyX3dpdGhpbih3b3JkLCBzdW0sIGRhdGUpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9d29yZCwgeT1zdW0sIGZpbGwgPSBkYXRlKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLCB5ID0gIuipnumguyIpICsKICBmYWNldF93cmFwKH5kYXRlLCBuY29sID0gMSwgc2NhbGVzPSJmcmVlIikgKyAKICBjb29yZF9mbGlwKCkrCiAgc2NhbGVfeF9yZW9yZGVyZWQoKSArCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiSGVpdGkgVEMgTGlnaHQiKSkKCnBsb3RfbWVyZ2UKYGBgCj4g55Sx5LiK5ZyW5Y+v5Lul55yL5Ye677yM5pyJ6Kix5aSa5LiN5ZCM5LmL6JmV77yM5L2G5Lmf5pyJ5LiA5Lqb6YeN6KSH5Ye654++55qE5a2X5b2Z44CCCgpgYGB7cn0KcGxvdF9tZXJnZSA8LSBkYXRhICU+JSAKICBncm91cF9ieShkYXRlKSAlPiUgCiAgdG9wX24oMjAsIHN1bSkgJT4lCiAgdW5ncm91cCgpICU+JSAKICBncm91cF9ieSh3b3JkKSAlPiUKICBmaWx0ZXIobigpPT0xKSAlPiUKICB1bmdyb3VwKCkgJT4lIAogIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBzdW0pKSAlPiUgCiAgZ2dwbG90KGFlcyh3b3JkLCBzdW0sIGZpbGwgPSBkYXRlKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLCB5PSLnuL3lkowiKSArCiAgZmFjZXRfd3JhcCh+ZGF0ZSwgbmNvbCA9IDEsIHNjYWxlcz0iZnJlZSIpICsgCiAgY29vcmRfZmxpcCgpKwogIHNjYWxlX3hfcmVvcmRlcmVkKCkgKwogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkhlaXRpIFRDIExpZ2h0IikpCgpwbG90X21lcmdlCmBgYAo+IOWwh+mHjeikh+eahOipnuW9meenu+mZpOW+jO+8jOWPr+S7peabtOa4healmueci+WIsOWcqCAwMS8yMCDoiIcgMDIvMjMg55qE6KiO6KuW5beu55Ww44CCCgojIENoLjQg5oOF57eS5oqY57ea5ZyWCj4g6YCP6YGO5paH5a2X5YiG5p6Q5bmz5Y+w5Y+v5Lul6Ly45Ye65q+P56+H5paH56ug55qE5oOF57eS5YiG5pW477yM5Zyo6LOH5paZ6aCQ6Ka96aCB6Z2i6YG45pOH44CM5paH56ugK+aDhee3kuOAjemBuOmgheWNs+WPr+W+l+WIsOizh+aWmeOAggoKIyMg6LyJ5YWl6LOH5paZCmBgYHtyfQpjc3Zfc2VuIDwtIGZyZWFkKCIuL2RhdGEvZGNhcmRfc3RvY2tfYXJ0U2VuLmNzdiIsIGVuY29kaW5nID0gIlVURi04IikKaGVhZChjc3Zfc2VuKQpgYGAKPiDos4fmlpnmrITkvY3ku4vntLnvvJo8YnI+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/jgIIKCmBgYHtyfQpkYXRhX3NlbiA8LSBjc3Zfc2VuICU+JSAKICBncm91cF9ieShhcnREYXRlKSAlPiUgCiAgc3VtbWFyaXNlKHBvc2l0aXZlID0gc3VtKHBvc2l0aXZlX2Vtb3Rpb25fZ3JhZGUpLCBuZWdhdGl2ZSA9IHN1bShuZWdhdGl2ZV9lbW90aW9uX2dyYWRlKSwgbmV1dHJhbCA9IHN1bShuZXV0cmFsX2Vtb3Rpb25fZ3JhZGUpKQoKaGVhZChkYXRhX3NlbikKYGBgCj4g5bCH6LOH5paZ5oyJ54Wn5pel5pyf5YiG576k5b6M77yM5Yqg57i95q+P5aSp55qE5LiJ56iu5oOF57eS5YiG5pW477yM5Lim6YCy6KGM5qiZ5rqW5YyW44CCCgpgYGB7cn0KZGF0YV9zZW4gJT4lIGdncGxvdChhZXMoeD0gYXJ0RGF0ZSkpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBwb3NpdGl2ZSwgY29sID0gInBvc2l0aXZlIikpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBuZWdhdGl2ZSwgY29sID0gIm5lZ2F0aXZlIikpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBuZXV0cmFsLCBjb2wgPSAibmV1dHJhbCIpKSArIAogIHNjYWxlX3hfZGF0ZShsYWJlbHMgPSBkYXRlX2Zvcm1hdCgiJVkvJW0vJWQiKSkgKwogICNzY2FsZV9jb2xvcl9kaXNjcmV0ZShuYW1lPSLmg4Xnt5LnqK7poZ4iLCBsYWJlbHMgPSBjKCJwb3NpdGl2ZSIsIm5lZ2F0aXZlIiwibmV1dHJhbCIpKSArIAogIGdndGl0bGUoIkRjYXJkIOiCoeelqOeci+advyDoqI7oq5bmg4Xnt5IiKSArIAogIHhsYWIoIuaXpeacnyIpICsgCiAgeWxhYigi5YiG5pW4IikgKyAKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJIZWl0aSBUQyBMaWdodCIpKQpgYGAKCgojIyDnt7Tnv5Lnt7Tnv5LvvZ4KPiDliZvliZvmiJHlgJHlsIfos4fmlpnmjInnhafml6XmnJ/liIbnvqTlvozvvIzliqDnuL3oqIjnrpfkuobmr4/lpKnnmoTkuInnqK7mg4Xnt5LliIbmlbjjgIIK5L2G5piv5oiR5YCR5Y+v5Lul55m854++5q+P5aSp55qE5paH56ug5pW46YeP6YCg5oiQ6L6t5b2Z55qE5pW46YeP5LiN5LiA5qij77yM5omA5Lul5oOF57eS5ZyW55qE6LWw5Yui5pyD5pyJ5b6I5aSn55qE6LW35LyP44CCCuiri+WQhOS9jeWwh+avj+aXpeeahOaDhee3kue4veWAvOmAsuihjOaomea6luWMluW+jOWGjeeVq+S4gOasoeaDhee3kueahOi1sOWLouWcluOAgjxicj48YnI+CuWFrOW8j++8mjxicj4KcG9zaXRpdmUgPSBwb3NpdGl2ZSAvIChwb3NpdGl2ZSArIG5lZ2F0aXZlICsgbmV1dHJhbCk8YnI+Cm5lZ2F0aXZlID0gbmVnYXRpdmUgLyAocG9zaXRpdmUgKyBuZWdhdGl2ZSArIG5ldXRyYWwpPGJyPgpuZXV0cmFsID0gbmV1dHJhbCAvIChwb3NpdGl2ZSArIG5lZ2F0aXZlICsgbmV1dHJhbCk8YnI+PGJyPgpoaW5077ya5Ymb5Ymbc3VtbWFyaXNl6YKj6YKK5Y+v5Lul6aCG5L6/6KiI566X5q+P5pel55qE5oOF57eS6Kme57i95pW4PGJyPgpoaW50Mu+8muacgOW+jOacg+eUqOWIsG11dGF0ZeWWlAoKYGBge3J9CiMjIyBDb2RlIEhlcmUgIyMjCmBgYAoKCiMgSG9tZXdvcmsK5Lul6K6A5pu45pyD54K65Zau5L2N77yM5L2/55So5paH5a2X5YiG5p6Q5bmz5Y+w77yM6Yed5bCN5pyJ6IiI6Laj55qE6K2w6aGM54is5Y+W6LOH5paZ77yM6LOH5paZ5L6G5rqQ5LiN6ZmQCgrmjqXokZfkvb/nlKjlubPlj7DpoJDopr3kuK3nmoTlnJblvaLoqablgZrnmoTlip/og73vvIzmjJHlh7rkuInlvLXmnInnibnoibLnmoTlnJbniYfvvIzmk7flj5bnlavpnaLlvozosrzliLB3b3Jk77yM5Lim5Zyo5q+P5by15ZyW54mH55qG6ZmE5LiK6Kqq5piOCgrkvZzmpa3ovYnmiJBQREbmoLzlvI/vvIzkuIrlgrPoh7PntrLlpKfjgIznrKzkuInpgLFIV+OAje+8jOavj+e1hOS4gOS6uuS4iuWCs+WNs+WPr+OAggoKCg==