jieba 是一個用於中文斷詞的 (Python) 套件。jiebaR
則是 jieba 的 R 版本。
使用 jiebaR
進行斷詞只須兩個步驟:
worker()
初始化斷詞設定segment()
將文字斷詞jiebaR::segment()
回傳一個 character vector,vector 內的每個元素都是一個被斷出來的詞:
#> [1] "失業" "的熊" "讚" "陪" "柯文" "哲看" "銀翼" "殺手"
jiebaR
的斷詞有時會不太精準,尤其是遇到專有名詞或是特殊詞彙時,這些詞彙時常會被斷開。若想避免這種情況,可以新增一份自訂辭典 (儲存在一份純文字檔,每個詞佔一行),例如 user_dict.txt
的內容如下:
熊讚
柯文哲
銀翼殺手
如此在 worker()
中設定自訂辭典,jiebaR
就不會將這些詞彙斷開:
#> [1] "失業" "的" "熊讚" "陪" "柯文哲" "看"
#> [7] "銀翼殺手"
我們的目標是建立一個 data frame 儲存文本資料。在這個 data frame 中,每一個 row 代表一篇文章,每個變項 (column) 記錄著該篇文章的某個資訊。根據文本資料的來源,該 data frame 可能會有不同數量的變項,例如,「文章發表日期」、「作者」、「標題」、「主題」等。但最重要的是,此 data frame 至少需具備兩個變項 — 「文章 id」與「(斷完詞的) 文章內文」。下方使用一個簡單的例子 (3 篇文章) 說明如何建立這種 data frame。
第一步是將 docs
內的各篇文章 (character vector) 進行斷詞,並在斷完詞後,將文章的內容存入另一個等長的 character vector 中。同篇文章中,被斷開的詞之間以一個空白字元分隔:
library(jiebaR)
# Data: 3 篇文章
docs <- c(
"蝴蝶和蜜蜂們帶著花朵的蜜糖回來了,羊隊和牛群告別了田野回家了,火紅的太陽也滾著火輪子回家了,當街燈亮起來向村莊道過晚安,夏天的夜就輕輕地來了。",
"朋友買了一件衣料,綠色的底子帶白色方格,當她拿給我們看時,一位對圍棋十分感與趣的同學說:「啊,好像棋盤似的。」「我看倒有點像稿紙。」我說。「真像一塊塊綠豆糕。」一位外號叫「大食客」的同學緊接著說。",
"每天,天剛亮時,我母親便把我喊醒,叫我披衣坐起。我從不知道她醒來坐了多久了。她看我清醒了,便對我說昨天我做錯了什麼事,說錯了什麼話,要我認錯,要我用功讀書。"
)
# Initialize jiebaR
seg <- worker()
docs_segged <- rep("", 3)
for (i in seq_along(docs)) {
# Segment each element in docs
segged <- segment(docs[i], seg)
# Collapse the character vector into a string, separated by space
docs_segged[i] <- paste0(segged, collapse = " ")
}
docs_segged
#> [1] "蝴蝶 和 蜜蜂 們 帶 著 花朵 的 蜜糖 回來 了 羊隊 和 牛群 告別 了 田野 回家 了 火紅 的 太陽 也 滾 著火 輪子 回家 了 當 街燈 亮 起來 向 村莊 道過 晚安 夏天 的 夜 就 輕輕地 來 了"
#> [2] "朋友 買 了 一件 衣料 綠色 的 底子 帶 白色 方格 當 她 拿給 我們 看時 一位 對 圍棋 十分 感與趣 的 同 學說 啊 好像 棋盤 似的 我 看 倒 有點像 稿紙 我 說 真 像 一塊塊 綠豆糕 一位 外號 叫 大 食客 的 同學 緊接著 說"
#> [3] "每天 天剛亮 時 我 母親 便 把 我 喊醒 叫 我 披衣 坐起 我 從不 知道 她 醒來 坐 了 多久 了 她 看 我 清醒 了 便 對 我 說 昨天 我 做 錯 了 什麼 事 說錯 了 什麼 話 要 我 認錯 要 我 用功讀書"
如此,我們就能使用這個斷完詞的 docs_segged
製作 data frame:
docs_df <- tibble::tibble(
doc_id = seq_along(docs_segged),
content = docs_segged
)
knitr::kable(docs_df, align = "c")
doc_id | content |
---|---|
1 | 蝴蝶 和 蜜蜂 們 帶 著 花朵 的 蜜糖 回來 了 羊隊 和 牛群 告別 了 田野 回家 了 火紅 的 太陽 也 滾 著火 輪子 回家 了 當 街燈 亮 起來 向 村莊 道過 晚安 夏天 的 夜 就 輕輕地 來 了 |
2 | 朋友 買 了 一件 衣料 綠色 的 底子 帶 白色 方格 當 她 拿給 我們 看時 一位 對 圍棋 十分 感與趣 的 同 學說 啊 好像 棋盤 似的 我 看 倒 有點像 稿紙 我 說 真 像 一塊塊 綠豆糕 一位 外號 叫 大 食客 的 同學 緊接著 說 |
3 | 每天 天剛亮 時 我 母親 便 把 我 喊醒 叫 我 披衣 坐起 我 從不 知道 她 醒來 坐 了 多久 了 她 看 我 清醒 了 便 對 我 說 昨天 我 做 錯 了 什麼 事 說錯 了 什麼 話 要 我 認錯 要 我 用功讀書 |
tidytext
套件是 R 生態圈中比較近期的 text mining 套件,它將 tidyverse 的想法運用到文本資料處理上,換言之,就是使用 data frame 的資料結構去表徵和處理文本資料。
tidytext
的方法處理文本資料有好有壞。
dplyr
與 ggplot2
於文本分析中,因而能快速地視覺化文本資料。壞處是,在 tidytext
framework 之下,文章的內部 (i.e. 詞彙與詞彙之間的) 結構會消失,因為它對於文本的想法是 bag-of-words。
tidytext
所倡導的儲存文本資料的格式是 one-token-per-document-per-row,亦即,在一個 data frame 中,每一橫列 (row) 是一篇文章中的一個 token。因此,若有兩篇文章,第一篇被斷成 38 個詞彙,第二篇被斷成 20 個詞彙,則共需要一個 58 列 (row) 的 data frame 來儲存這兩篇文章。
一般而言,tidytext 的架構適合用於與詞頻有關的分析,例如,計算文章的 lexical diversity 或是透過情緒詞的詞頻進行情緒分析。
透過 tidytext::unnest_tokens()
,可以將 docs_df
中儲存之 (已斷詞) 文本資料,變成 tidytext format,i.e.,one-token-per-document-per-row 的 data frame:
library(tidytext)
library(dplyr)
tidy_text_format <- docs_df %>%
unnest_tokens(output = "word", input = "content",
token = "regex", pattern = " ") # 以空白字元作為 token 分隔依據
tidy_text_format
#> # A tibble: 139 x 2
#> doc_id word
#> <int> <chr>
#> 1 1 蝴蝶
#> 2 1 和
#> 3 1 蜜蜂
#> 4 1 們
#> 5 1 帶
#> 6 1 著
#> 7 1 花朵
#> 8 1 的
#> 9 1 蜜糖
#> 10 1 回來
#> # … with 129 more rows
可以使用 dplyr
的 group_by()
與 summarise()
計算詞頻表:
#> # A tibble: 99 x 2
#> word n
#> <chr> <int>
#> 1 了 11
#> 2 我 11
#> 3 的 6
#> 4 她 3
#> 5 說 3
#> 6 一位 2
#> 7 什麼 2
#> 8 便 2
#> 9 叫 2
#> 10 和 2
#> # … with 89 more rows
#> # A tibble: 99 x 2
#> word n
#> <chr> <int>
#> 1 了 11
#> 2 我 11
#> 3 的 6
#> 4 她 3
#> 5 說 3
#> 6 一位 2
#> 7 什麼 2
#> 8 便 2
#> 9 叫 2
#> 10 和 2
#> # … with 89 more rows
計算每篇文章的 lexical diversity (type-token ratio):
tidy_text_format %>%
group_by(doc_id, word) %>%
summarise(n = n()) %>% # Calculate token freq.
ungroup() %>%
group_by(doc_id) %>%
summarise(TTR = n() / sum(n)) # Calculate type/token ratio
#> # A tibble: 3 x 2
#> doc_id TTR
#> <int> <dbl>
#> 1 1 0.814
#> 2 2 0.896
#> 3 3 0.667
傳統 R 的 text mining 生態圈中,使用的是另一種 (高階) 資料結構儲存文本資料 — 語料庫 (corpus)。不同的套件有自己定義 corpus 的方式,且各自進行文本分析的流程與想法差異頗大。目前最流行、支援最多的兩個套件是 quanteda
與 tm
。其中,quanteda
在中文支援以及說明與教學文件的完整度較高。
下方為 quanteda
套件的一些使用範例。欲比較完整地了解 quanteda
,請閱讀 quanteda tutorials。
quanteda
的好處在於它保留了文章的內部結構,例如,可以透過 quanteda::kwic()
去檢視特定詞彙或是片語出現的語境。與此同時,quanteda
也提供許多 bag-of-words 想法之下的函數。quanteda
的缺點在於其內容龐雜,需要一些語料庫語言學的背景知識以及相當的時間摸索才能掌握。library(quanteda)
# 將 data frame 轉換成 Corpus object
quanteda_corpus <- corpus(docs_df,
docid_field = "doc_id",
text_field = "content")
# tokenize the corpus
# (因為已先斷過詞, 使用 "fastestword", 即 空白字元 作為 tokenize 的方式)
qcorp_tokens <- tokens(quanteda_corpus, "fastestword")
kwic(qcorp_tokens, "我", window = 5, valuetype = "regex") %>%
knitr::kable(align = "c")
docname | from | to | pre | keyword | post | pattern |
---|---|---|---|---|---|---|
2 | 15 | 15 | 白色 方格 當 她 拿給 | 我們 | 看時 一位 對 圍棋 十分 | 我 |
2 | 29 | 29 | 學說 啊 好像 棋盤 似的 | 我 | 看 倒 有點像 稿紙 我 | 我 |
2 | 34 | 34 | 我 看 倒 有點像 稿紙 | 我 | 說 真 像 一塊塊 綠豆糕 | 我 |
3 | 4 | 4 | 每天 天剛亮 時 | 我 | 母親 便 把 我 喊醒 | 我 |
3 | 8 | 8 | 時 我 母親 便 把 | 我 | 喊醒 叫 我 披衣 坐起 | 我 |
3 | 11 | 11 | 便 把 我 喊醒 叫 | 我 | 披衣 坐起 我 從不 知道 | 我 |
3 | 14 | 14 | 喊醒 叫 我 披衣 坐起 | 我 | 從不 知道 她 醒來 坐 | 我 |
3 | 25 | 25 | 了 多久 了 她 看 | 我 | 清醒 了 便 對 我 | 我 |
3 | 30 | 30 | 我 清醒 了 便 對 | 我 | 說 昨天 我 做 錯 | 我 |
3 | 33 | 33 | 便 對 我 說 昨天 | 我 | 做 錯 了 什麼 事 | 我 |
3 | 44 | 44 | 說錯 了 什麼 話 要 | 我 | 認錯 要 我 用功讀書 | 我 |
3 | 47 | 47 | 話 要 我 認錯 要 | 我 | 用功讀書 | 我 |
Lexical Diversity (比較上方 tidytext 詞頻表)
#> document TTR
#> 1 1 0.8139535
#> 2 2 0.8958333
#> 3 3 0.6666667
繪製文字雲: