日語文字探勘之介紹

An Introduction to Japanese Text Mining


本文由邱郁婷編輯

本文摘自Mark Ravina的〈An Introduction to Japanese Text Mining(日語文字探勘之介紹)〉,由邱郁婷統整後撰文。


文章介紹

此篇文章介紹了如何使用R語言進行文字探勘,對於運用日語資源進行研究的學生以及研究人員可謂是十分實用。文章共分為10個章節,以下將針對各章節進行介紹:

一、 基礎規則與知識

所謂的「R語言」是一種十分好用又獨樹一幟的程式語言,其不同於一致又具有邏輯性的Python,R語言是由一心想解決問題的統計學所編寫而成,而非出自重視邏輯與一致性的電腦工程師或軟體工程師之手,因此其獨特又怪異,與其說是人工語言,更像是人類語言。

R語言能有效地解決人文與社會科學領域的問題,例如統計分析、文字探勘、數據視覺化、空間分析、網路分析,甚至是圖片分析。透過RStudio與IDE(整合開發環境,Integrated development environment),使得R語言的學習變得更加簡單。RStudio除了結合GUI的優點與舊式指令介面之外,若在RStudio中輸入內容並按下tab鍵,其會試著以合適的命令或變量(Variable)自動完成輸入之編碼(Code)。然而,若是較小的項目,使用GUI會比使用R語言來得簡單。舉例來說,透過網頁工具,如Voyant Tools或青空文庫的語言學介面等來進行基礎的文字探勘會更加容易,此二者皆含有「點擊」介面,使單一文本的基礎分析變得十分簡易。GUI的限制主要來自於「規模」的大小,若是想要知道單一文本中「社會」一詞出現在「女性」一詞附近的次數,使用點擊介面會比寫編碼來得快速;相反地,若是想進行分析的文本數量多,且欲將分析結果進行彙整,則是書寫編碼會更加快速。此外,即便經過長久的時間後欲再次檢閱數據,也無需擔心是否能夠重現,只要重新運行編碼即可得到相同結果,這是GUI較難達成的。

二、 數據結構

此項目介紹了如何使用RStudio進行編碼的書寫與儲存,操作範本為19世紀的日本雜誌《明六雜誌》,來源於日本國立國語研究所的現代日語語料庫,且其文本已經過標記化(Tokenized)。

首先,透過檔案→新檔案→R腳本開啟新的編碼視窗,並將《明六雜誌》的編碼輸入至視窗之中,執行之後即可將文本輸入至RStudio之中。若雙擊「Meiroku.df」的字樣,則可將其顯示於左上角的文字編輯器(Source Pane)。有些語法十分淺顯易懂,例如「read.table」便是要R讀取表格的指令;以括弧括起來的部分(即所謂的參數)是告訴R數據具有信頭(Header),即第一行中包含了變量名稱。

執行完上述步驟後,將得到一名為「Meiroku.df」的資料框架,其類似於電子表格,每一行皆為一案例或觀測值(Observation),且每一列皆為一變量。可透過組合資料框架的名稱和列的名稱辨認資料框架的列,並加入「$」符號,「$」符號在於告訴R指定列(或向量)與特定的資料框架相關,而非指金錢。舉例來說,「Meiroku.df$author」指的是Meiroku.df中的作者列,輸入此指令後,將會列出所有文本的作者,若是同一個作者寫了複數作品,則會出現複數次。

「Meiroku.df$author」為一維數據對象,因此如欲抓取單個元素,只需一個數字,而在R語言中,以中括號「[]」標示出元件的位置。舉例來說,若欲找出《明六雜誌》第二篇文章的作者,則輸入「Meiroku.df$author[2]」,則可找出文章的作者。此外,若欲取得一系列的值,則可使用代表結合與連接的運算符號「c」。舉例來說,2~5的範圍指定方式即為「c(2:5)」;若欲指定2及5,則輸入「c(2,5)」。在進行範圍指定時,有時不輸入c也可以。

資料框架為二維對象,因此辨別一項元件時需要用到2種標記,一為行數(Row Number),二為列數(Column Number)。一般來說,通用的語法為「檔案框名稱[行數,列數]」。假設作者的資訊在資料框架Meiroku.df的第4列,若要取得第二篇文章的作者資訊,則輸入「Meiroku.df[2,4]」即可。此同樣可結合運算符號「c」一起使用,若要指定範圍,則輸入「檔案框名稱[c(行數,列數:行數,列數)]」即可。

R的其中一個特點,是括號中沒有一樣東西能夠代表一切。例如若欲查看第三列的所有項目,輸入「[,4]」即會顯示出抓取結果。這看似是R的錯誤設定,但其確實是十分好用的工具。最後,使用者可一名稱指定列,例如透過「Meiroku.df[c(1:6),"year"]」指令來指定年分。

三、 賦值與子集

R語言中,使用者所給予的變量或值稱為「賦值」,而賦值的運算符號為小於符號與破折號的組合「<-」。舉例來說,若設x為3,則寫作「x <- 3」;若設x為一系列值,則使用運算符號c,寫作「x <- c(1,3)」;如欲賦予一字母值(alphanumeric)或一系列字母值,則使用引號,如「city_names <- c(“Tokyo”,“Moscow”,“Des Moines”)」。

繼上述,語法中的「x」與「city_names」皆稱為向量,若查看右上方「環境(Environment)」標籤的「值」欄位,便會發現city_names之標記為chr(意義字母,Meaning Character);x之標記為num(意義數字,Meaning Numeric)。向量必須為同質,即須皆為純字母或純數字,若是在數字中出現字母,則R會將向量視為chr並拒絕進行數學計算。此外,資料框架中每一列或行皆可視為是一個向量,基本上資料框架是一個整齊的矩形向量集。

若欲使用R語言從資料框架中找出特定數據,可使用等號「=」。舉例來說,若欲從《明六雜誌》的作者資料中查看哪些元件為「西周」,則語法為:

Meiroku.df$author==“西周”

輸入後即會以「TRUE」和「FALSE」來告訴使用者欄位中的值是否為西周。然而,雙個等號與單個等號在使用上有極大的差異,單個等號更像是一個指令,告訴R讓Meiroku.df$author「等於」西周,而這樣的操作會覆蓋掉整個作者欄位的值,因此雖然有時會使用等號來指定值,使用賦值運算符號「<-」還是更加安全與清楚。

切回前提,得到「TRUE」與「FALSE」的結果後,即可使用此資訊得出「TRUE」的指數(即其位置),即:

which(Meiroku.df$author=="西周")

知道指數後,運用其結果提取數據或使之子集化。首先將指數配給變量,語法為:

special_subset <- which(Meiroku.df$author=="西周")

隨後便可找出所有西周所寫的文章標題,語法為:

Meiroku.df$title[special_subset]

若得出一系列數據,如上述的「西周所寫之文章標題」,即可再透過「資料框架名稱[行數, 列數]」或「資料框架名稱[子集,]」的語法建立一個新的資料框架,如:

Nishi_articles.df <- Meiroku.df[special_subset,]

逗號之後無任何東西則代表著「所有內容」,若欲寫出更密集的語法,則可跳過建立子集的步驟,直接將選取標準放入括號之中,如:

Nishi_articles.df <- Meiroku.df[which(Meiroku.df$author=="西周"),]

四、 函數與子集

為了進行更複雜的文字探勘,將會使用到工具包與其函數。首先須先安裝工具包,並在每次重新開啟R或清除R環境時加載工具包。安裝工具包時的指令有兩種,一為「install.package」,其如同將書放在書櫃或桌上,僅需要進行一次操作即可;二為「library」,如同將書取出並打開,每次此用時皆需重新輸入一次。此處安裝之工具包為「stringr」,則指令為:

install.package(“stringr”)
library(stringr)

Stringr工具包中具有一系列處理字串(String)的邏輯命名函數,例如可用於指定引數名稱的「str_count」,語法「string =」會告訴函數查找的位置;而「pattern =」則會告訴R查找的內容。假設要計算單獨存在或複合字詞中的「女」字時,則使用的語法為:

str_count(string = Meiroku.df$text, pattern = "女")

然而,有時也會看見編碼省略這些外顯標記(Explicit Argument),舉例來說,輸入「str_count(Meiroku.df$text, "女")」也能得出與前述例子相同的結果,因R是按位置推斷引數的字串與模式,不過實際上在使用時,使用外顯標記會更容易一些。

繼上述,使用指令計算出「女」字的結果後,亦可將其加入至Meiroku.df資料框架中,建立一個新的「女」欄位。此處將用「$」符號來將向量放至資料框架,語法為:

Meiroku.df$女 <- str_count(string = Meiroku.df$text, pattern = "女")

會了這些之後,即可運用同樣的語法來建立其他資料框架。舉例來說,將《明六雜誌》中「女」字數大於「自由」字數的所有文章找出後,建立成另一個資料框架。語法為:

Meiroku.df$自由 <- str_count(string = Meiroku.df$text, pattern = "自由") Meiroku_subset.df <- Meiroku.df[which(Meiroku.df$女>Meiroku.df$自由),]

當然也可以再新增新的條件,例如從「女」大於「自由」的文章中,挑出「森有禮」所寫的文章標題,語法的寫法有2種,一種是拆成多步驟書寫,語法為:

Meiroku_subset_step_one.df <- Meiroku.df[which(Meiroku.df$女>Meiroku.df$自由),] Meiroku_subset_step_two.df <- Meiroku_subset_step_one.df[which(Meiroku_subset_step_one.df$author=="森有礼"),] Meiroku_subset_step_two.df$title

第二種則是使用「&」符號來連接不同的語法,並減少一個步驟,即:

Meiroku_subset.df <- Meiroku.df[which(Meiroku.df$女>Meiroku.df$自由 & Meiroku.df$author=="森有礼"),] Meiroku_subset_step_two.df$title」

在找出森有禮的文章時,亦可加入代表「或」的符號「|(大寫的斜線\)」來指定森有禮與另一個作者的文章。舉例來說,另一位要找的作者是加藤弘之,則語法為:

Meiroku.df$title[which(Meiroku.df$author=="加藤弘之" | Meiroku.df$author=="森有礼")]

五、 主機代管(Colocation)-基礎數據視覺化

R語言有許多實用的數據視覺化工具包,此項目將使用圖形工具包「ggplot」及可使圖像互動的「ploty」,首先進行安裝:

install.packages("ggplot2") install.packages("plotly")

安裝完後,將工具喚出:

library(ggplot2) library(plotly)

此項目以「女」與「自由」的數量進行視覺化,語法為:

ggplot(data=Meiroku.df, mapping=aes(x=女,y=自由,label=author)) + geom_point()

輸入後,即會得到一張散點圖,x軸為女,y軸為自由。ggplot是一個相當強大的語法,其語法之格式為:

ggplot(data=資料框架名, mapping=aes(x=字詞一, y=字詞二)) + geom_point()  

需要注意的是,若欲將某項目進行視覺化,則需先取得字數,且有時圖表上可能會顯示不出Unicode字形。若要比較「男」與「女」,並同時解決字體問題,可用「+」號來增加語法,如:

ggplot(data=Meiroku.df, mapping=aes(x=女,y=男)) + geom_point() + theme_grey(base_family="Osaka")

輸入後即可出現一圖表,一般情況下,上術語法適用於大多數的Windows系統與MacOS系統。

除此之外,還可透過簡單的調整,讓圖表具有互動性,並將ggplot輸出至ggplotly。同時亦會加入語法「label=author」,讓鼠標移到對應項目時能顯示出其作者名稱,即:

plot_output <- ggplot(data=Meiroku.df, mapping=aes(x=女,y=男,label=author)) + geom_point() ggplotly(plot_output)

然而,若某些文章具有完全相同的值,則圖表上可能不會出現數量相符合的點,即看不見某些觀察值,這樣的問題稱為「過度繪製(Overplotting)」。碰到此狀況,可以將單詞的數量重新以總字數之比例(百分比)計算來解決,這種方法有時稱為「常態化(Normalizing)」,語法為:

Meiroku.df$男 <- str_count(string = Meiroku.df$text, pattern = "男")/nchar(Meiroku.df$text)100 Meiroku.df$女 <- str_count(string = Meiroku.df$text, pattern = "女")/nchar(Meiroku.df$text)100

計算完百分比後,再運行一次編碼,這次將指令ggplot包含於ggplotly中寫作一行,即:

ggplotly(ggplot(data=Meiroku.df, mapping=aes(x=女,y=男,label=author)) + geom_point())

這裡補充一點,某些高端的工程師會傾向於僅寫一行編碼,因其認此較為簡潔。此外,任何的數字變量都可用於x與y。

散點圖是許多複雜分析的基礎,如相關分析、回歸分析(OLS)、因素分析、主成分分析(PCA)及聚類分析皆可透過散點圖來理解。

六、 正規表達之搜索

前幾個項目皆是使用確切的字詞來進行搜尋,例如輸入「女」來找到「女」;輸入「自由」來找到「自由」。不過若是想透過其中一個字來搜尋複合字詞,例如以「女」字搜尋以女字開頭、以女字結尾或含有女字的複合字詞時,便可透過正規表達式達成這個目的,因正規表達式是指定文字類型或位置來進行搜索,而非確切的文字。

使用正規表達式時,句號「.」代表任意文字(包含空格)。舉個簡單的例子,同樣先以「library(stringr)」叫出工具包,接著給定一串文字「これはペンです(這是筆)」,並輸入以下語法:

str_extract_all(string = "これはペンです", pattern = "は", simplify = TRUE)

隨後結果即會顯示句中的「は」。

若是輸入以下語法:

str_extract_all(string = "これはペンです", pattern = ".は.", simplify = TRUE)

則Pattern部分的句號代表欲查詢的是「は」前後一個字,因此顯示之結果將會是「れはペ」。

正規表達式亦可用於搜尋多個可能的文字,以下將1890年明治天皇頒布的《教育敕語》作為操作範本。首先以「rescript <- “文章完整內容”」進行賦值,若想搜尋文中的「皇」與「朕」,可以透過2種方法達成。第一種是使用中括號「[]」來表示「或」,即[皇朕],語法寫作:

str_extract_all(string = rescript, pattern = "..[皇朕]..", simplify = TRUE)

另一種代表「或」的符號則是使用括號「()」與一直線「|(大寫的斜線\)」,即(皇|朕),語法寫作:

str_extract_all(string = rescript, pattern = "..(皇|朕)..", simplify = TRUE)

進行正規表達式時,除了句號之外,亦可使用大括號「{}」加數字來表示前後顯示之字數,例如欲搜尋「民」字的前後4個字,以句點表示為:

str_extract_all(string = rescript, pattern = “....民....”

以大括號與數字表示則為:

str_extract_all(string = rescript, pattern = “.{4}民.{4}”

另外,大括號亦可用於指定數字範圍。《教育敕語》中以朕開頭,因此若輸入「..朕..」會找不到開頭的部分,但若是以大括號輸入「str_extract_all(string = rescript, pattern = ".{0,4}朕.{0,4}")」,以此方式找「朕」字前0至4個字,即可找到開頭的部分。

米字號「*」在正規表達式中亦是一個十分強大的符號,代表「0或多次」。若欲查詢「朕」到「民」的所有內容,則輸入:

str_extract_all(string = rescript, pattern = "朕.*民")

這種搜索方式,出現的結果會停在最後一次出現的「民」字,不過使用者可透過問號「?」來指定「不貪婪(Non-greedy)」或「懶惰(Lazy)」的搜尋方式。若輸入「str_extract_all(string = rescript, pattern = "朕.*?民")」,則結果僅會出現所有「朕」到「民」的句子,而不會把中間其他不相關的內容顯示出來。

正規表達亦可用來指定平片假名與漢字,「str_extract_all(string = rescript, pattern = "\p{Han}{2}", simplify = TRUE)」即是指定《教育敕語》中連續2字皆為漢字的字詞,如「皇祖」、「皇宗」或「臣民」等;而「str_extract_all(string = rescript, pattern = "民\p{Katakana}.", simplify = TRUE)」則是在指定「民」字後為片假名的字詞。

接下來試著使用正規表達來搜尋《明六雜誌》。若輸入「str_extract_all(string = Meiroku.df$text[120:130], pattern = "\p{Han}{0,1}女\p{Han}{0,1}")」,則會在第120至130篇文章中找出含有「女」字的字詞。在查看搜尋結果時,會看見雙重的中括號,如「[[1]]」或「[[2]]」,這與先前提及的資料框架與向量有關,向量是數據的行或列,而資料框架則是向量的組合,呈現矩形且整齊。然而,在使用「str_extract_all」時,由於結果並非呈矩形亦非整齊,因此無法呈現(Return)出資料框架。具體來說,有些文章並無使用到「女」字,因此會出現空行,當向量不均勻時,R便無法製作資料框架,因此只呈現出一列表。列表上的雙重括號即標記了文章的編號,後面的向量則是文章中包含「女」字的字詞。

不過,列表雖然較為彈性,但在分析數據時,需要更易於管理的資料模式,因此可透過「unlist」將所有數據融合成一個向量。同樣以《明六雜誌》為例:

unlist(str_extract_all(string = Meiroku.df$text, pattern = "\p{Han}{0,1}女\p{Han}{0,1}"))

輸入上述語法後,則會出現所有包含「女」字的字詞,但不會顯示出文章的編號,因此也不存在空行與不均勻的問題。

透過unlist得到整齊劃一的數據後,即可簡單地取得詞頻表格,接下來將使用「table」指令製作表格,再將其轉成資料框架。假設欲查看字尾為女的2字字詞之詞頻,則語法為:

onna_instances <- unlist(str_extract_all(string = Meiroku.df$text, pattern = ".女")) onna.df <- data.frame(table(onna_instances))

透過文字編輯器打開「onna.df」資料框架(表格)後,點擊「上下排序」的按鍵,即可查看哪些複合字詞的詞頻最高。

若是想在R語言中對資料框架進行排序,則可使用指令「order」,其語法幾乎與第三項中的「which」相同,只不過「order」是用來改變排序,而非進行子集化,即:

onna.df[order(-onna.df$Freq),]

七、 建立DTM(文件矩陣,Documentary Term Matrix)

R可用來進行重複性作業,以《明六雜誌》為例,其中包含了超過15,000個特殊用語,但使用者不可能寫超過15,000行指令來進行處理,因此使用R語言,便可以7或8行的指令來完成這項作業。此項目將以《明六雜誌》為範本,介紹如何建立DTM,操作上較為複雜,需要將列表與矩陣轉換成數據框。

在執行前,首先需要一份《明六雜誌》的特殊用語列表,為此須先將所有單獨的文章融合成一字串,此操作會運用語法「paste」來進行,即文本的等效加法(Equivalent of addition)。

一般來說,使用R時,僅有數字能夠進行加減,文字則無法執行這項操作,不過使用了「paste」之後,就能將文字整合成一串字串。例如輸入「paste(“a”, “b”, “c”)」,則結果會顯示「##[1] “a b c”」。由於單獨的文章亦為向量的元件,因此需要加上標記「collapse」才能夠將文章整合為一字串,例如:

Complete_meiroku <- paste(Meiroku.df$text, collapse = “ ” )

整合之後,即可使用語法「str_split」將字串拆成單個字詞,並以空格分隔,語法為:

Complete_meiroku_split <- str_split(string = Complete_meiroku, pattern = “ ”)」 Complete_meiroku_split <- unlist(Complete_meiroku_split)

進行完此操作後,「Complete_meiroku_split」會變成包含173,197個元件的向量,這是《明六雜誌》全部155篇文章的總字數。

得出文本的總字數之後,即可透過「table」快速地計算出所有字詞的詞頻,語法為:

Meiroku_frequency <- data.frame(table(Complete_meiroku_split))

有了詞頻列表後,需要再得出一份記載了所有特殊用語的列表,其語法為:

Meiroku_unique_words <- unique(Complete_meiroku_split)

值得注意的是,特殊用語較少,僅有15,603個元件。

建立DTM前,需先搜尋所有文本的每一個字詞。以「女」為例,首先需讓R分辨「女」字以及包含女字的「複合字詞」,執行這項操作時,將使用到正規表達中代表「字邊界(Word Boundary)」的「\b」,其包含了空格、標點符號以及字串的開頭與結尾,因此若輸入「\b女」,則會找出文本中的「女」字及以女開頭的字串。接下來的操作中,將會在「Meiroku_unique_words」中所有特殊用語的前後加上「\b」,語法為:

Meiroku_search_terms <- paste("\b",Meiroku_unique_words,"\b",sep="")

接著,可透過指令「sapply」來讓R運行整個向量。由於R需要在155篇文章中查找173,197個文字,因此這項操作可能會花上一至兩分鐘。指令「sapply」有2個主要標記,一為「Meiroku_search_term」,即指定搜索字詞的向量;二為「str_count(string = Meiroku.df$text, pattern = x)」,用於指定R應將函數應用至所有的字詞上。完整的語法為:

dtm.matrix <- sapply(X = Meiroku_search_terms, FUN = function(x) str_count(string = Meiroku.df$text, pattern = x)) dtm.df <- as.data.frame(dtm.matrix)

出現的結果稱之為「dtm.df」,即為DTM的資料框架。若仔細看此資料框架,會發現其有155行(每篇文章為一行),173,197列(每個字詞為一列)。

「sapply」與「str_count」不同,無法從名稱看出其作用,不過卻是一個非常方便的指令,其能將「Meiroku_search_terms」作為「x」,將函數「function str_count(Meiroku.df$text, x)」套用至「x」的每個元件上。

接下來,針對字數進行「正規化(Normalize)」,將其除以每個文本的總字數。為了將這項作業自動化,首先需要取得155篇文章中每一篇的總字數,再將15,603列 (特殊用語)中的每一列除以其對應文章之總數。此函數的關鍵有3項:

(一) X=dtm.matrix → 告訴R語言行的位置

(二) MARGIN = 1 → 告訴R語言執行指令時為一次一行

(三) x/sum(x)*100 → 告訴R語言將一行的元件除以該行的總和,再乘上100

完整的語法為:

dtm_norm.matrix <- apply(X = dtm.matrix, MARGIN = 1, FUN = function(x) x/sum(x)*100) dtm_norm.df <- as.data.frame(t(dtm_norm.matrix))」

此資料框架是以「列」來呈現,而非「行」,因此若想將其轉換回以「行」呈現,可用代表「轉置(Transpose)」的語法「t」來達成。

為了充分地運用這份DTM,此處將其加至初始資料框架「Meiroku.df」的原數據中。由於兩者的行數相同,R可透過「cbind」,即綁定列來將兩者結合,語法為: Meiroku_dtm.df <- cbind(Meiroku.df[,c(1:5)], dtm_norm.df)

完成這步驟之後,即可透過詞頻來搜尋《明六雜誌》。

DTM建立完畢後,將其儲存以便於後續使用,儲存使用之語法為:

write.table(Meiroku_dtm.df, file = "\儲存之文件位置\Meiroku_dtm_df.txt", col.names = TRUE, sep = "\t")

八、 聚合與排序

有了DTM之後,還可進行聚合(Aggregating),此項目以《明六雜誌》的作者為範本進行操作。首先查看文本共有幾位作者,語法為:

unique(Meiroku.df$author)

接著依照作者將詞頻進行彙整,隨後將會得到每個作者的總字數,並對DTM進行「重新正規化(Re-normalize)」。為了進行這項操作,需建立一個僅包含作者與字數的資料框「temp.df」。需注意的是,由於作者的名字並非數字,因此需先告訴R不對作者名或任何其他的元數據進行計算。此外,因前五列是元數據,所以亦需告訴R從第六列開始運行至最後一列,此處使用的語法為「c(6:ncol(temp.df))」;為了對作者名進行聚合,接著需告訴R著手於第一列,語法為「Meiroku_dtm.df[,4]」;「FUN=sum」用於告訴R按照作者取得總和;最後,用於聚合的指令即為「aggregate」。完整之語法為:

Meiroku_author_dtm.df <- aggregate(Meiroku_dtm.df[,c(6:ncol(Meiroku_dtm.df))], by = list(Meiroku_dtm.df[,4]), FUN = sum)

若想對DTM進行正規化,可再次使用「apply」將計算值轉換成百分比,並將公式「x/sum(x)*100」套至每一行的每個元件;另,由於此矩陣為純數字,因此需將作者名加回資料框架,將兩資料框架進行結合,即:

Meiroku_author.dtm <- apply(X = Meiroku_author_dtm.df[,c(2:ncol(Meiroku_author_dtm.df))], MARGIN = 1, FUN = function(x) x/sum(x)*100) Meiroku_author_dtm_temp.df <- as.data.frame(t(Meiroku_author.dtm)) Meiroku_author_dtm.df <- cbind(Meiroku_author_dtm.df[1],Meiroku_author_dtm_temp.df)

結合完畢後,對列名稱進行整理,將第一列命名為「作者」,語法為:

colnames(Meiroku_author_dtm.df) <- c("authors",Meiroku_unique_words)

此外,若要清除列名稱,則可使用標記「\b」進行。

最後,可繪製出圖表來查看各作者使用「男」或「女」字的頻率,語法為:

ggplotly( ggplot(Meiroku_author_dtm.df, aes(女,男, label=authors)) + geom_point())

九、 標記與審查

日語是不以空格進行分隔的語言之一,言下之意即是字詞與標記(Tokens)之間不存在空格,雖然也能在不進行「標記(Tokenization)」的狀況下進行大略的分析,不過江文本進行切割之後,以字詞進行分析的效率會更好。進行標記的流程如下:

(一) 將文本導入至R中,並清除HTML、XML與其他非文本之數據

(二) 將清理完畢後的文件上傳至NINJAL

(三) 將輸出格式(出力形式)選取為「csv」以及「UTF-8」

(四) 點選「分析檔案(ファイルから解析)」按鍵

(五) 將csv檔導入至R中,並將其轉為資料框架

此項目之操作說明將以《痴人之愛》作為範本進行解釋,若要取得文本,可至青空文庫下載。在開始之前,第一步是將文本導入至R語言當中,語法為:

library(readr) chijin_xml <- read_lines("https://www.aozora.gr.jp/cards/001383/files/58093_62049.html" ,locale = locale(encoding = "SHIFT-JIS"))

上述語法當中的「locale」將會處理「SHIFT-JIS」編碼的問題。

由於XML中充滿了格式標籤(Formatting Tag),所以需要使用正規表達式將其清除。HTML及XML中的標籤多為「大於」或「小於」,因此可只搜尋這些標籤,並將其清除,語法為:

chijin_xml <- str_replace_all(chijin_xml, "<.*?>", "")

搜尋完畢後,現在要刪除文本頭尾的非文本數據,為此將建立一個只有小說內文的新項目。查看文檔的內容時,可以透過語法「chijin_xml[行數:行數]」的方式查看每一行的內容,經查詢後得知小說的正文自第26行開始,第2466行結束。藉此,即可將其子集化,語法為:

chijin <- chijin_xml[26:(2450+16)]

再使用語法「head」及「tail」來選定頭尾,使用之語法為「head(chijin)」及「tail(chijin)」。

導入文本至R時,還有另外一種方法,即自青空文庫下載壓縮的zip檔,再將解壓縮後的txt檔導入進R語言,語法為:

chijin_txt <- read_lines("chijinno_ai.txt", locale = locale(encoding = "SHIFT-JIS"))

導入之後同樣選取出內文的頭尾,僅保留小說的內容,雖然語法中的指數稍有不同,不過操作過程與前述內容相同,語法為:

chijin <- chijin_txt[20:2460]

建立了「chijin」之後,接著要在R語言中使用章節標記(Chapter Break Mark)。青空文庫的文本中,章節標記是以「#5字縮排(#5字下げ)」開頭,但在標記的過程中,這可能會導致錯亂的情況發生,因此將以正規表達式來替換,語法為:

chijin <- str_replace(chijin,"^[#5字下げ].*$","CHAPTERZZZZZ")

語法中使用「^」來標記行的開頭,使用「$」來標記行的結尾,而「^[#5字下げ].*$」則可抓取所有以「#5字下げ」開始的字串;此外,此處以「CHAPTERZZZZZ」作為「#5字下げ」的替換字串。替換用的字串可自行設定,其既不會出現在原文中,NINJAL標記工具亦不會對其進行分析。完成這項操作之後,即可儲存文本以上傳至NINJAL,儲存語法為:

writeLines(chijin, "new_chijin.txt")

儲存完畢後,即可上傳至NINJAL標記工具進行文本分析。上傳後,於輸出格式處選取「csv」及「UTF-8」之欄位;預設情況下,該網站亦有許多語言細節,如詞類、發音或動詞型態等,這些項目可以提供十分詳細的資訊,舉例來說「詞類(品詞)」會標示出字詞的詞性;「動詞型態-原形(書字形(基本形))」則會標示出動詞的原形與其轉換之形態。在進行文本分析時,也應選取合適的標記工具,以「chijin」來看,最合適的便是「現代文(現代語)」。

選取完畢後,點即「分析檔案」之按鍵後,即會開始進行文本分析。下載分析完的檔案後,則會自動以Excel開啟檔案,不過不需理會,接下來將使用R進行處理。點擊R的「導入資料集(Impor Dataset)」下拉式方塊,再點選「文本(基礎)(From Text(base))」。隨後即會跳出一視窗,輸入欲設定之文本名稱(原文設定為Tanizaki_Chijin_no_ai),並取消選取「字串視為因子(Strings as Factors)」後,即可點擊「開啟」將文本導入。若欲以腳本的方式將文本導入,則輸入:

Tanizaki <- read.csv("webchamame_20190414234619.csv", comment.char="#", stringsAsFactors=FALSE)

進行詞頻分析時,可以使用標記的原形(動詞原形-表層形)或詞幹形式(動詞原形-基本形)。要將單個觀察值(標記)轉換成詞頻列表很簡單,首先需要使用「table」建立詞頻表,再以「data.frame」轉換成資料框架,即:

Tanizaki_words.df <- data.frame(table(Tanizaki$書字形..表層形.))

接著使用「order」對資料框架進行排序,其語法幾乎與子集相同,此處希望能依照頻率高至低排序,語法為:

Tanizaki_words.df <- Tanizaki_words.df[order(-Tanizaki_words.df$Freq),]

此處輸入「Tanizaki_words.df[1:50,]」挑選出前50個頻率最高的項目,其為《痴人之愛》中最常見的漢字組合,後續將使用到「str_detect」(返回二進制TRUE或FALSE)以及正規表達式「\p{Han}」,輸入時會將「str_detect」包在「which」的語法中以取得指數,並運用其將資料框架子集化,語法為:

Tanizaki_kanji.df <-Tanizaki_words.df[which(str_detect(Tanizaki_words.df$Var1,"\p{Han}")),] Tanizaki_kanji.df[1:50,]

這類基礎的分析亦具有其意義,透過基礎分析可發現文本中有許多第一人稱、第二人稱及專有名詞。

此外,亦可運用「詞類」列將部分字詞集合成資料框架,並以「原形」進行計算。以動詞為例,語法為:

Tanizaki_verbs.df <- Tanizaki[which(Tanizaki$品詞=="動詞-一般"),] Tanizaki_verbs.df <- data.frame(table(Tanizaki_verbs.df$書字形.基本形.)) Tanizaki_verbs.df <- Tanizaki_verbs.df[order(-Tanizaki_verbs.df$Freq),] Tanizaki_verbs.df[1:20,]

透過上述語法,可從結果看出「云う(說)」是最常見的動詞,有了數據便可計算出其佔動詞總數的百分比。語法為:

Tanizaki_verbs.df$Freq[which(Tanizaki_verbs.df$Var1=="云う")]/ sum(Tanizaki_verbs.df$Freq)*100

接著用同樣的手法分析文本中的專有名詞,由於文本中有人名及地名,此處將用到「str_detect」來尋找「名詞-固有名詞」,藉此抓取出「名詞-固有名詞-人名」及「名詞-固有名詞-地名」。語法為:

Tanizaki_names.df <- Tanizaki[which(str_detect(Tanizaki$品詞, "名詞-固有名詞-")),] Tanizaki_names.df <- data.frame(table(Tanizaki_names.df$書字形.基本形.)) Tanizaki_names.df <- Tanizaki_names.df[order(-Tanizaki_names.df$Freq),] Tanizaki_names.df[1:10,]

從結果上來看,最常出現的專有名詞是人名的「ナオミ」,得出這項結果後,現在來查看ナオミ的動詞相對次數(Relative Frequency)。此處將建立一個因子(Factor)來將字詞鎖定在頻率的排序中,因子是含有潛在數值的字串,例如「first」、「second」、「junior」或「senior」等皆是能夠做出良好因子的例子。因子介於字串與數字之間,雖然方便使用,但也容易混淆,例如搞錯因子的面向(Aspect),在需要文字的時候卻得到數字,因此需要建立新的變量(Variable),即《痴人之愛》中的動詞以頻率進行排序,語法為:

Tanizaki_verbs.df$ordered <- reorder(Tanizaki_verbs.df$Var1, Tanizaki_verbs.df$Freq)

得到新的變量之後,即可在「ggplot」中使用,此處使用指令「ggplot」將頻率高的前20個動詞製作成資料框架,並利用「coord_flip()」製作出長條圖。語法為:

ggplot(Tanizaki_verbs.df[c(1:20),], aes(ordered,Freq)) + geom_col() + coord_flip() + theme_gray(base_family = "Osaka") + ggtitle("Verb frequency in Naomi") + xlab("Verbs") + ylab("Frequency")

最後,試著查看小說中的用語是如何隨時間變化。此處先將所有行結合成一個長字串,再使用先前提到的標籤「CHAPTERZZZZZ」將其分割。語法為:

Tanizaki_text <- paste(Tanizaki$書字形..表層形., collapse = " ") Tanizaki_chapters <- data.frame(t(str_split(Tanizaki_text, "CHAPTER", simplify = TRUE))) colnames(Tanizaki_chapters) <- "text" Tanizaki_chapters$number <- as.numeric(row.names(Tanizaki_chapters))

接著將字串進行計算,並繪製出計算圖,加上色彩使之變成一張有趣的數據視覺化圖表。此步驟將以文本中的「日本」與「西洋」一詞作為範例,並在指令「ggplot」中選定資料框架,在「geom_line」中選定變量。語法為:

Tanizaki_chapters$日本 <- str_count(Tanizaki_chapters$text,"日本") Tanizaki_chapters$西洋 <- str_count(Tanizaki_chapters$text,"西洋") ggplot(Tanizaki_chapters, aes(西洋,日本)) + geom_point() + theme_grey(base_family = "Osaka")

十、 附錄:關鍵的正規表達式

此項目共有4個小節,前3個小節主要介紹各語法的代表意義,最後一小節則說明如何運用正規表達式清除網頁數據,以下針對各節進行說明:

(一) 日語的特殊正規表達式:

1. \\p{Hiragana} → 平假名

2. \\p{Katakana} → 片假名

3. \\p{Han} → 漢字

4. [\u3000-\u303F] → 日語標點符號與其他符號

5. [\uFF5F-\uFF9F] → 半形片假名與標點符號

(二) 一般的正規表達式:

1. \\s → 空格

2. \\S → 無空格

3. \\w → 文字,如字母、數字或底線等

4. \\W → 非文字,如空格、標點符號等

5. \\d → 數字

6. \\D → 非數字

7. \\b → 字邊界,意義幾乎與「非文字」相同,不過包含了行的頭尾

8. \\B → 字邊界的否定,即兩字詞間或兩非文字間的任何位置

(三) 位置正規表達式:

1. ^ → 起始線

2. $ → 結尾線

(四) 清除網頁數據:

若直接從網頁上抓取檔案,可能會充滿html的標籤,因此可透過正規表達式將其移除,此項目將以青空文庫中由林芙美子所寫的《浮雲》為操作範例。R語言能很好地辨識UTF-8編碼,不過青空文庫使用的是Shift-JIS編碼,即便如此readr工具包仍能允許使用者指定編碼,其中的指令「read_lines」與base R的「readLines」幾乎相同,語法為:

library(readr) messy.ukigumo <- read_lines("https://www.aozora.gr.jp/cards/000006/files/1869_33656.html", locale = locale(encoding = "SHIFT_JIS")) messy.ukigumo[20:25] messy.ukigumo[50:55]

若要清除將近所有的html,則需使用到正規表達式的字串「<.*?>」,這能夠搜尋到括號「<__ and >」中的所有東西,語法為:

clean.ukigumo <- str_replace_all(string = messy.ukigumo, pattern = "<.*?>",replacement = "") clean.ukigumo[20:25] clean.ukigumo[50:55]

進行完以上步驟後,除了開頭的原數據之外,此文本已能夠進行後續之使用。