この記事では,quantedaの特徴と機能の概要を説明します.より詳しい説明は,quanteda.ioにある記事を参照してください.

パッケージのインストール

quantedaCRANからインストールできます.GUIのRパッケージインストーラを使用してインストールするか,次のコマンドを実行します.

install.packages("quanteda") 

GitHubから最新の開発バージョンをインストールする方法については,https://github.com/kbenoit/quanteda を参照してください.

インストールが推奨されるパッケージ

quantedaには連携して機能を拡張する一連のパッケージがあり,それらをインストールすることをお薦めします.

  • readtext:多くの入力形式からテキストデータをRに簡単に読み込むパッケージ
  • spacyr:PythonのspaCyライブラリを使用した自然言語解析のためのパッケージで,品詞タグ付け,固有表現抽出,および係り受け関係の解析などができる
  • quantedaDataquantedaの本記事内の説明で使用する追加のテキストデータ

    devtools::install_github("kbenoit/quantedaData")
  • LIWCalike: Linguistic Inquiry and Word Count (LIWC) アプローチによるテキスト分析のR実装

    devtools::install_github("kbenoit/LIWCalike")

コーパスの作成

まず,quantedaを読み込んで,パッケージの関数とデータにアクセスできるようにします.

library(quanteda)

利用可能なコーパス

quantedaにはテキストを読み込むためのシンプルで強力なパッケージ,readtextがあります.このパッケージのreadtext()は,ローカス・ストレージやインターネットからファイル読み込み,corpus()にデータ・フレームを返します.

readtext()で利用可能なファイルやデータの形式:

  • テキスト(.txt)ファイル
  • コンマ区切り値(.csv)ファイル
  • XML形式のデータ
  • JSON形式のFacebook APIのデータ
  • JSON形式のTwitter APIのデータ
  • 一般的なJSONデータ

quantedaのコーパスを生成する関数である corpus()は,以下の種類のデータを読み込むことができます.

  • 文字列ベクトル(例:readtext以外のツールを使用して読み込んだテキスト)
  • tmパッケージの VCorpusコーパスオブジェクト
  • テキスト列と他の文書に対応したメタデータを含むデータ・フレーム

文字列からコーパスを作成

コーパスを作成する最も簡単な方法は,corpus()を用いて,すでにRに読み込まれた文字列ベクトル作成することです.文字列ベクトルをRに取り込む方法はさまざまなので,高度なRユーザーは,コーパスをいろいろな方法で作り出せます.

次の例では,quantedaパッケージに含まれているイギリスの政党が2010年の総選挙のために発行したマニフェストのテキストデータ(data_char_ukimmig2010)からコーパスを作成しています.

myCorpus <- corpus(data_char_ukimmig2010)  # テキストからコーパスを作成
summary(myCorpus)
## Corpus consisting of 9 documents:
## 
##          Text Types Tokens Sentences
##           BNP  1125   3280        88
##     Coalition   142    260         4
##  Conservative   251    499        15
##        Greens   322    679        21
##        Labour   298    683        29
##        LibDem   251    483        14
##            PC    77    114         5
##           SNP    88    134         4
##          UKIP   346    723        27
## 
## Source:  /home/kohei/packages/quanteda/docs/articles/pkgdown/* on x86_64 by kohei
## Created: Fri Dec 15 11:54:28 2017
## Notes:

コーパスを作成したあとでも,docvarsを用いると,必要に応じて文書に対応した変数をこのコーパスに追加することができます.

たとえば,Rのnames()関数を使って文字ベクトル(data_char_ukimmig2010)の名前を取得し,これを文書変数(docvar())に追加することができます.

docvars(myCorpus, "Party") <- names(data_char_ukimmig2010)
docvars(myCorpus, "Year") <- 2010
summary(myCorpus)
## Corpus consisting of 9 documents:
## 
##          Text Types Tokens Sentences        Party Year
##           BNP  1125   3280        88          BNP 2010
##     Coalition   142    260         4    Coalition 2010
##  Conservative   251    499        15 Conservative 2010
##        Greens   322    679        21       Greens 2010
##        Labour   298    683        29       Labour 2010
##        LibDem   251    483        14       LibDem 2010
##            PC    77    114         5           PC 2010
##           SNP    88    134         4          SNP 2010
##          UKIP   346    723        27         UKIP 2010
## 
## Source:  /home/kohei/packages/quanteda/docs/articles/pkgdown/* on x86_64 by kohei
## Created: Fri Dec 15 11:54:28 2017
## Notes:

分析の対象となる文書変数ではではないけれども,文書の属性として残しておきたいと思うメタデータも,docvars()を使って,コーパスに追加することができます.

metadoc(myCorpus, "language") <- "english"
metadoc(myCorpus, "docsource")  <- paste("data_char_ukimmig2010", 1:ndoc(myCorpus), sep = "_")
summary(myCorpus, showmeta = TRUE)
## Corpus consisting of 9 documents:
## 
##          Text Types Tokens Sentences        Party Year _language
##           BNP  1125   3280        88          BNP 2010   english
##     Coalition   142    260         4    Coalition 2010   english
##  Conservative   251    499        15 Conservative 2010   english
##        Greens   322    679        21       Greens 2010   english
##        Labour   298    683        29       Labour 2010   english
##        LibDem   251    483        14       LibDem 2010   english
##            PC    77    114         5           PC 2010   english
##           SNP    88    134         4          SNP 2010   english
##          UKIP   346    723        27         UKIP 2010   english
##               _docsource
##  data_char_ukimmig2010_1
##  data_char_ukimmig2010_2
##  data_char_ukimmig2010_3
##  data_char_ukimmig2010_4
##  data_char_ukimmig2010_5
##  data_char_ukimmig2010_6
##  data_char_ukimmig2010_7
##  data_char_ukimmig2010_8
##  data_char_ukimmig2010_9
## 
## Source:  /home/kohei/packages/quanteda/docs/articles/pkgdown/* on x86_64 by kohei
## Created: Fri Dec 15 11:54:28 2017
## Notes:

metadoc()を用いると,文書メタデータのフィールドを自由に定義することができますが,単一の値(“english”)を文書変数(“language”)に付与するときには,Rが値を繰り替えして全ての文書に同じ値を付与していることに注意してください.

独自の文書メタデータのフィールド(docsource)を作成するために,quantedaの関数であるndoc()を使ってコーパスに含まれる文書の総数を取得しています.ndoc()は,nrow()ncol()などのRの標準の関数と同じような方法で動作するように設計されています.

readtextパッケージを用いたファイルの読み込み

require(readtext)

# Twitter json
mytf1 <- readtext("~/Dropbox/QUANTESS/social media/zombies/tweets.json")
myCorpusTwitter <- corpus(mytf1)
summary(myCorpusTwitter, 5)
# generic json - needs a textfield specifier
mytf2 <- readtext("~/Dropbox/QUANTESS/Manuscripts/collocations/Corpora/sotu/sotu.json",
                  textfield = "text")
summary(corpus(mytf2), 5)
# text file
mytf3 <- readtext("~/Dropbox/QUANTESS/corpora/project_gutenberg/pg2701.txt", cache = FALSE)
summary(corpus(mytf3), 5)
# multiple text files
mytf4 <- readtext("~/Dropbox/QUANTESS/corpora/inaugural/*.txt", cache = FALSE)
summary(corpus(mytf4), 5)
# multiple text files with docvars from filenames
mytf5 <- readtext("~/Dropbox/QUANTESS/corpora/inaugural/*.txt", 
                  docvarsfrom = "filenames", sep = "-", docvarnames = c("Year", "President"))
summary(corpus(mytf5), 5)
# XML data
mytf6 <- readtext("~/Dropbox/QUANTESS/quanteda_working_files/xmlData/plant_catalog.xml", 
                  textfield = "COMMON")
summary(corpus(mytf6), 5)
# csv file
write.csv(data.frame(inaugSpeech = texts(data_corpus_inaugural), 
                     docvars(data_corpus_inaugural)),
          file = "/tmp/inaug_texts.csv", row.names = FALSE)
mytf7 <- readtext("/tmp/inaug_texts.csv", textfield = "inaugSpeech")
summary(corpus(mytf7), 5)

コーパスオブジェクトの使い方

コーパスの原理

quantedaのコーパスは,元の文書をユニコード(UTF-8)に変換し,文書に対するメタデータと一緒に格納しすることで、ステミングや句読点の削除などの処理よって変更されないテキストデータの静的な保管庫になるように設計されています.これによって,コーパスから文書を抽出して新しいオブジェクトを作成した後でも,コーパスには元のデータが残り,別の分析を,同じコーパスを用いて行うことができます.

コーパスから文書を取り出すためには,texts()と呼ばれる関数を使用します.

texts(data_corpus_inaugural)[2]
##                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              1793-Washington 
## "Fellow citizens, I am again called upon by the voice of my country to execute the functions of its Chief Magistrate. When the occasion proper for it shall arrive, I shall endeavor to express the high sense I entertain of this distinguished honor, and of the confidence which has been reposed in me by the people of united America.\n\nPrevious to the execution of any official act of the President the Constitution requires an oath of office. This oath I am now about to take, and in your presence: That if it shall be found during my administration of the Government I have in any instance violated willingly or knowingly the injunctions thereof, I may (besides incurring constitutional punishment) be subject to the upbraidings of all who are now witnesses of the present solemn ceremony.\n\n "

summary()により,コーパス内のテキストの要約を行うことができます.

summary(data_corpus_irishbudget2010)
## Corpus consisting of 14 documents:
## 
##                                   Text Types Tokens Sentences year debate
##        2010_BUDGET_01_Brian_Lenihan_FF  1953   8641       374 2010 BUDGET
##       2010_BUDGET_02_Richard_Bruton_FG  1040   4446       217 2010 BUDGET
##         2010_BUDGET_03_Joan_Burton_LAB  1624   6393       307 2010 BUDGET
##        2010_BUDGET_04_Arthur_Morgan_SF  1595   7107       343 2010 BUDGET
##          2010_BUDGET_05_Brian_Cowen_FF  1629   6599       250 2010 BUDGET
##           2010_BUDGET_06_Enda_Kenny_FG  1148   4232       153 2010 BUDGET
##      2010_BUDGET_07_Kieran_ODonnell_FG   678   2297       133 2010 BUDGET
##       2010_BUDGET_08_Eamon_Gilmore_LAB  1181   4177       201 2010 BUDGET
##     2010_BUDGET_09_Michael_Higgins_LAB   488   1286        44 2010 BUDGET
##        2010_BUDGET_10_Ruairi_Quinn_LAB   439   1284        59 2010 BUDGET
##      2010_BUDGET_11_John_Gormley_Green   401   1030        49 2010 BUDGET
##        2010_BUDGET_12_Eamon_Ryan_Green   510   1643        90 2010 BUDGET
##      2010_BUDGET_13_Ciaran_Cuffe_Green   442   1240        45 2010 BUDGET
##  2010_BUDGET_14_Caoimhghin_OCaolain_SF  1188   4044       176 2010 BUDGET
##  number      foren     name party
##      01      Brian  Lenihan    FF
##      02    Richard   Bruton    FG
##      03       Joan   Burton   LAB
##      04     Arthur   Morgan    SF
##      05      Brian    Cowen    FF
##      06       Enda    Kenny    FG
##      07     Kieran ODonnell    FG
##      08      Eamon  Gilmore   LAB
##      09    Michael  Higgins   LAB
##      10     Ruairi    Quinn   LAB
##      11       John  Gormley Green
##      12      Eamon     Ryan Green
##      13     Ciaran    Cuffe Green
##      14 Caoimhghin OCaolain    SF
## 
## Source:  /Users/kbenoit/Dropbox (Personal)/GitHub/quanteda/* on x86_64 by kbenoit
## Created: Wed Jun 28 22:04:18 2017
## Notes:

summary()の出力をデータ・フレームとして保存し,基本的な記述統計を描画することができます.

tokenInfo <- summary(data_corpus_inaugural)
if (require(ggplot2))
    ggplot(data=tokenInfo, aes(x = Year, y = Tokens, group = 1)) + geom_line() + geom_point() +
        scale_x_continuous(labels = c(seq(1789,2012,12)), breaks = seq(1789,2012,12) ) 
## Loading required package: ggplot2

# Longest inaugural address: William Henry Harrison
tokenInfo[which.max(tokenInfo$Tokens), ] 
## Corpus consisting of 58 documents:
## 
##           Text Types Tokens Sentences Year President     FirstName
##  1841-Harrison  1896   9144       210 1841  Harrison William Henry
## 
## Source:  Gerhard Peters and John T. Woolley. The American Presidency Project.
## Created: Tue Jun 13 14:51:47 2017
## Notes:   http://www.presidency.ucsb.edu/inaugurals.php

コーパスに対する操作

コーパスの結合

+演算子を用いると,簡単に二個のコーパスを連結できます.コーパスが異なる構造を持つ場合でも,文書変数が失われることはなく,コーパスのメタデータも引き継がれます.

library(quanteda)
mycorpus1 <- corpus(data_corpus_inaugural[1:5])
mycorpus2 <- corpus(data_corpus_inaugural[53:58])
mycorpus3 <- mycorpus1 + mycorpus2
summary(mycorpus3)
## Corpus consisting of 11 documents:
## 
##             Text Types Tokens Sentences
##  1789-Washington   625   1538        23
##  1793-Washington    96    147         4
##       1797-Adams   826   2578        37
##   1801-Jefferson   717   1927        41
##   1805-Jefferson   804   2381        45
##     1997-Clinton   773   2449       111
##        2001-Bush   621   1808        97
##        2005-Bush   773   2319       100
##       2009-Obama   938   2711       110
##       2013-Obama   814   2317        88
##       2017-Trump   582   1660        88
## 
## Source:  Combination of corpuses mycorpus1 and mycorpus2
## Created: Fri Dec 15 11:54:30 2017
## Notes:

コーパス内の文書の一部の文書の抽出

corpus_subset()により,文書変数に適用される論理条件に基づいて文書を抽出することができます.

summary(corpus_subset(data_corpus_inaugural, Year > 1990))
## Corpus consisting of 7 documents:
## 
##          Text Types Tokens Sentences Year President FirstName
##  1993-Clinton   642   1833        81 1993   Clinton      Bill
##  1997-Clinton   773   2449       111 1997   Clinton      Bill
##     2001-Bush   621   1808        97 2001      Bush George W.
##     2005-Bush   773   2319       100 2005      Bush George W.
##    2009-Obama   938   2711       110 2009     Obama    Barack
##    2013-Obama   814   2317        88 2013     Obama    Barack
##    2017-Trump   582   1660        88 2017     Trump Donald J.
## 
## Source:  Gerhard Peters and John T. Woolley. The American Presidency Project.
## Created: Tue Jun 13 14:51:47 2017
## Notes:   http://www.presidency.ucsb.edu/inaugurals.php
summary(corpus_subset(data_corpus_inaugural, President == "Adams"))
## Corpus consisting of 2 documents:
## 
##        Text Types Tokens Sentences Year President   FirstName
##  1797-Adams   826   2578        37 1797     Adams        John
##  1825-Adams  1003   3152        74 1825     Adams John Quincy
## 
## Source:  Gerhard Peters and John T. Woolley. The American Presidency Project.
## Created: Tue Jun 13 14:51:47 2017
## Notes:   http://www.presidency.ucsb.edu/inaugurals.php

コーパス内の文書の探索

kwic()(keywords-in-context)は単語の検索を行い,その単語が現れる文脈を表示します.

kwic(data_corpus_inaugural, "terror")
##                                                                     
##     [1797-Adams, 1325]              fraud or violence, by | terror |
##  [1933-Roosevelt, 112] nameless, unreasoning, unjustified | terror |
##  [1941-Roosevelt, 287]      seemed frozen by a fatalistic | terror |
##    [1961-Kennedy, 866]    alter that uncertain balance of | terror |
##     [1981-Reagan, 813]     freeing all Americans from the | terror |
##   [1997-Clinton, 1055]        They fuel the fanaticism of | terror |
##   [1997-Clinton, 1655]  maintain a strong defense against | terror |
##     [2009-Obama, 1632]     advance their aims by inducing | terror |
##                                   
##  , intrigue, or venality          
##  which paralyzes needed efforts to
##  , we proved that this            
##  that stays the hand of           
##  of runaway living costs.         
##  . And they torment the           
##  and destruction. Our children    
##  and slaughtering innocents, we
kwic(data_corpus_inaugural, "terror", valuetype = "regex")
##                                                                           
##     [1797-Adams, 1325]                   fraud or violence, by |  terror  
##  [1933-Roosevelt, 112]      nameless, unreasoning, unjustified |  terror  
##  [1941-Roosevelt, 287]           seemed frozen by a fatalistic |  terror  
##    [1961-Kennedy, 866]         alter that uncertain balance of |  terror  
##    [1961-Kennedy, 990]               of science instead of its |  terrors 
##     [1981-Reagan, 813]          freeing all Americans from the |  terror  
##    [1981-Reagan, 2196]        understood by those who practice | terrorism
##   [1997-Clinton, 1055]             They fuel the fanaticism of |  terror  
##   [1997-Clinton, 1655]       maintain a strong defense against |  terror  
##     [2009-Obama, 1632]          advance their aims by inducing |  terror  
##     [2017-Trump, 1117] civilized world against radical Islamic | terrorism
##                                     
##  | , intrigue, or venality          
##  | which paralyzes needed efforts to
##  | , we proved that this            
##  | that stays the hand of           
##  | . Together let us explore        
##  | of runaway living costs.         
##  | and prey upon their neighbors    
##  | . And they torment the           
##  | and destruction. Our children    
##  | and slaughtering innocents, we   
##  | , which we will eradicate
kwic(data_corpus_inaugural, "communist*")
##                                                                   
##   [1949-Truman, 834] the actions resulting from the | Communist  |
##  [1961-Kennedy, 519]             -- not because the | Communists |
##                            
##  philosophy are a threat to
##  may be doing it,

上記の要約では,“Year”と“President”は各文書に結び付けられた変数です. docvars()でこのような変数にアクセスできます.

# inspect the document-level variables
head(docvars(data_corpus_inaugural))
##                 Year  President FirstName
## 1789-Washington 1789 Washington    George
## 1793-Washington 1793 Washington    George
## 1797-Adams      1797      Adams      John
## 1801-Jefferson  1801  Jefferson    Thomas
## 1805-Jefferson  1805  Jefferson    Thomas
## 1809-Madison    1809    Madison     James
# inspect the corpus-level metadata
metacorpus(data_corpus_inaugural)
## $source
## [1] "Gerhard Peters and John T. Woolley. The American Presidency Project."
## 
## $notes
## [1] "http://www.presidency.ucsb.edu/inaugurals.php"
## 
## $created
## [1] "Tue Jun 13 14:51:47 2017"

quantedaDataをインストールすることで,より多くのコーパスを試すことができます.

コーパスから特長を抽出

文書のスケーリングなどの統計分析を行うためには,それぞれの文書の特長をまとめた行列を作成する必要があります. quantedaでは,このような行列を生成するために dfm()を使います. dfmはdocument-feature matrixの略で,行が文書(document),列が特長(feature)となる行列です.行と列をこのように定義する理由は,データ分析では行が分析単位になり,各列が分析対象になる変数となるのが一般的だからです.多くのソフトウェアでは,この行列をdocument-term matrixと呼びます.quantedaが語(term)でなはく特長(feature)という用語を使うのは,特長のほうが一般性を持つ用語だからです.テキスト分析では,単語,語幹,単語の集合,Nグラム,品詞など様々なものが文書の特長となります.

文書のトークン化

テキストを簡単にトークン化するために,quantedatokens()と呼ばれる強力なコマンドを提供します.この関数は,文字ベクトルのトークンのリストからなる中間オブジェクトを生成します.ここで,リストの一つ一つの要素は入力された文書に対応しています.

tokens()は意図して保守的に設計されており、ユーザーが明示的に指示を与えないかぎりは,要素を削除しません.

txt <- c(text1 = "This is $10 in 999 different ways,\n up and down; left and right!", 
         text2 = "@kenbenoit working: on #quanteda 2day\t4ever, http://textasdata.com?page=123.")
tokens(txt)
## tokens from 2 documents.
## text1 :
##  [1] "This"      "is"        "$"         "10"        "in"       
##  [6] "999"       "different" "ways"      ","         "up"       
## [11] "and"       "down"      ";"         "left"      "and"      
## [16] "right"     "!"        
## 
## text2 :
##  [1] "@kenbenoit"     "working"        ":"              "on"            
##  [5] "#quanteda"      "2day"           "4ever"          ","             
##  [9] "http"           ":"              "/"              "/"             
## [13] "textasdata.com" "?"              "page"           "="             
## [17] "123"            "."
tokens(txt, remove_numbers = TRUE,  remove_punct = TRUE)
## tokens from 2 documents.
## text1 :
##  [1] "This"      "is"        "in"        "different" "ways"     
##  [6] "up"        "and"       "down"      "left"      "and"      
## [11] "right"    
## 
## text2 :
## [1] "@kenbenoit"     "working"        "on"             "#quanteda"     
## [5] "2day"           "4ever"          "http"           "textasdata.com"
## [9] "page"
tokens(txt, remove_numbers = FALSE, remove_punct = TRUE)
## tokens from 2 documents.
## text1 :
##  [1] "This"      "is"        "10"        "in"        "999"      
##  [6] "different" "ways"      "up"        "and"       "down"     
## [11] "left"      "and"       "right"    
## 
## text2 :
##  [1] "@kenbenoit"     "working"        "on"             "#quanteda"     
##  [5] "2day"           "4ever"          "http"           "textasdata.com"
##  [9] "page"           "123"
tokens(txt, remove_numbers = TRUE,  remove_punct = FALSE)
## tokens from 2 documents.
## text1 :
##  [1] "This"      "is"        "$"         "in"        "different"
##  [6] "ways"      ","         "up"        "and"       "down"     
## [11] ";"         "left"      "and"       "right"     "!"        
## 
## text2 :
##  [1] "@kenbenoit"     "working"        ":"              "on"            
##  [5] "#quanteda"      "2day"           "4ever"          ","             
##  [9] "http"           ":"              "/"              "/"             
## [13] "textasdata.com" "?"              "page"           "="             
## [17] "."
tokens(txt, remove_numbers = FALSE, remove_punct = FALSE)
## tokens from 2 documents.
## text1 :
##  [1] "This"      "is"        "$"         "10"        "in"       
##  [6] "999"       "different" "ways"      ","         "up"       
## [11] "and"       "down"      ";"         "left"      "and"      
## [16] "right"     "!"        
## 
## text2 :
##  [1] "@kenbenoit"     "working"        ":"              "on"            
##  [5] "#quanteda"      "2day"           "4ever"          ","             
##  [9] "http"           ":"              "/"              "/"             
## [13] "textasdata.com" "?"              "page"           "="             
## [17] "123"            "."
tokens(txt, remove_numbers = FALSE, remove_punct = FALSE, remove_separators = FALSE)
## tokens from 2 documents.
## text1 :
##  [1] "This"      " "         "is"        " "         "$"        
##  [6] "10"        " "         "in"        " "         "999"      
## [11] " "         "different" " "         "ways"      ","        
## [16] "\n"        " "         "up"        " "         "and"      
## [21] " "         "down"      ";"         " "         "left"     
## [26] " "         "and"       " "         "right"     "!"        
## 
## text2 :
##  [1] "@kenbenoit"     " "              "working"        ":"             
##  [5] " "              "on"             " "              "#quanteda"     
##  [9] " "              "2day"           "\t"             "4ever"         
## [13] ","              " "              "http"           ":"             
## [17] "/"              "/"              "textasdata.com" "?"             
## [21] "page"           "="              "123"            "."

tokensには個々の文字をトークン化するオプションもあります.

tokens("Great website: http://textasdata.com?page=123.", what = "character")
## tokens from 1 document.
## text1 :
##  [1] "G" "r" "e" "a" "t" "w" "e" "b" "s" "i" "t" "e" ":" "h" "t" "t" "p"
## [18] ":" "/" "/" "t" "e" "x" "t" "a" "s" "d" "a" "t" "a" "." "c" "o" "m"
## [35] "?" "p" "a" "g" "e" "=" "1" "2" "3" "."
tokens("Great website: http://textasdata.com?page=123.", what = "character", 
         remove_separators = FALSE)
## tokens from 1 document.
## text1 :
##  [1] "G" "r" "e" "a" "t" " " "w" "e" "b" "s" "i" "t" "e" ":" " " "h" "t"
## [18] "t" "p" ":" "/" "/" "t" "e" "x" "t" "a" "s" "d" "a" "t" "a" "." "c"
## [35] "o" "m" "?" "p" "a" "g" "e" "=" "1" "2" "3" "."

もしくは,一文ごとにトークン化するオプションもあります.

# sentence level         
tokens(c("Kurt Vongeut said; only assholes use semi-colons.", 
         "Today is Thursday in Canberra:  It is yesterday in London.", 
         "En el caso de que no puedas ir con ellos, ¿quieres ir con nosotros?"), 
         what = "sentence")
## tokens from 3 documents.
## text1 :
## [1] "Kurt Vongeut said; only assholes use semi-colons."
## 
## text2 :
## [1] "Today is Thursday in Canberra:  It is yesterday in London."
## 
## text3 :
## [1] "En el caso de que no puedas ir con ellos, ¿quieres ir con nosotros?"

文書行列の作成

文書をトークン化は中間的な処理であり,ほとんどのユーザーはこれを省いて、すぐに文書行列を作成したいと考えるでしょう. このために,quantedaは,自動的にトークン化を実行し,文書行列を作成するdfm()と呼ばれるスイスアーミーナイフのように便利な関数を持っています.保守的なtokens()とは異なり,dfm()はデフォルトで大文字から小文字への置換や,句読点を除去などの操作を適用します.また,dfm()からtokens()の全てのオプションを利用できます.

myCorpus <- corpus_subset(data_corpus_inaugural, Year > 1990)

# make a dfm
myDfm <- dfm(myCorpus)
myDfm[, 1:5]
## Document-feature matrix of: 7 documents, 5 features (0% sparse).
## 7 x 5 sparse Matrix of class "dfm"
##               features
## docs           my fellow citizens   , today
##   1993-Clinton  7      5        2 139    10
##   1997-Clinton  6      7        7 131     5
##   2001-Bush     3      1        9 110     2
##   2005-Bush     2      3        6 120     3
##   2009-Obama    2      1        1 130     6
##   2013-Obama    3      3        6  99     4
##   2017-Trump    1      1        4  96     4

dfm()の追加のオプションには,ストップワードの削除(remove)や語のステミング(stem)が含まれます.

# make a dfm, removing stopwords and applying stemming
myStemMat <- dfm(myCorpus, remove = stopwords("english"), 
                 stem = TRUE, remove_punct = TRUE)
myStemMat[, 1:5]
## Document-feature matrix of: 7 documents, 5 features (17.1% sparse).
## 7 x 5 sparse Matrix of class "dfm"
##               features
## docs           fellow citizen today celebr mysteri
##   1993-Clinton      5       2    10      4       1
##   1997-Clinton      7       8     6      1       0
##   2001-Bush         1      10     2      0       0
##   2005-Bush         3       7     3      2       0
##   2009-Obama        1       1     6      2       0
##   2013-Obama        3       8     6      1       0
##   2017-Trump        1       4     5      3       1

removeによっては,文書行列から除外するトークンを指定します.stopwords()は,幾つかの言語で定義されたストップワードのリストを返します(残念ながら日本語は含まれていませんが,ユーザーが定義した辞書を使うことはできます).

head(stopwords("english"), 20)
##  [1] "i"          "me"         "my"         "myself"     "we"        
##  [6] "our"        "ours"       "ourselves"  "you"        "your"      
## [11] "yours"      "yourself"   "yourselves" "he"         "him"       
## [16] "his"        "himself"    "she"        "her"        "hers"
head(stopwords("russian"), 10)
##  [1] "и"   "в"   "во"  "не"  "что" "он"  "на"  "я"   "с"   "со"
head(stopwords("arabic"), 10)
##  [1] "فى"  "في"  "كل"  "لم"  "لن"  "له"  "من"  "هو"  "هي"  "قوة"

文書行列の表示

RStudioの“Environment”パネル,またはRのView()を用いることで,dfmに格納された値を見ることができます.dfmに対してplot()を用いると,wordcloudを使ってワードクラウドが表示されます.

mydfm <- dfm(data_char_ukimmig2010, remove = stopwords("english"), remove_punct = TRUE)
mydfm
## Document-feature matrix of: 9 documents, 1,547 features (83.8% sparse).

頻度が最も高い特長を見るには,topfeatures()を使います.

topfeatures(mydfm, 20)  # 20 top words
## immigration     british      people      asylum     britain          uk 
##          66          37          35          29          28          27 
##      system  population     country         new  immigrants      ensure 
##          27          21          20          19          17          17 
##       shall citizenship      social    national         bnp     illegal 
##          17          16          14          14          13          13 
##        work     percent 
##          13          12

dfmをtextplot_wordcloud()に渡すことで,ワードクラウドを描画できます.この関数は,オブジェクトや引数をwordcloudパッケージのwordcloud()に送るので,ワードクラウドの表示を変更することもできます.

set.seed(100)
textplot_wordcloud(mydfm, min.freq = 6, random.order = FALSE,
                   rot.per = .25, 
                   colors = RColorBrewer::brewer.pal(8,"Dark2"))

変数による文書のグループ化

quantedaでは,dfmを作成する際に,文書変数の値によって文書をグループ化するすることができます.

byPartyDfm <- dfm(data_corpus_irishbudget2010, groups = "party", 
                  remove = stopwords("english"), remove_punct = TRUE)

また、以下のように,dfmを語の頻度順に並べ替えて,中身を確かめられます.

dfm_sort(byPartyDfm)[, 1:10]
## Document-feature matrix of: 5 documents, 10 features (0% sparse).
## 5 x 10 sparse Matrix of class "dfm"
##        features
## docs    people budget government public minister tax economy pay jobs
##   FF        23     44         47     65       11  60      37  41   41
##   FG        78     71         61     47       62  11      20  29   17
##   LAB       69     66         36     32       54  47      37  24   20
##   SF        81     53         73     31       39  34      50  24   27
##   Green     15     26         19      4        4  11      16   4   15
##        features
## docs    billion
##   FF         32
##   FG         21
##   LAB        34
##   SF         29
##   Green       3

辞書による語のグループ化

肯定的な映画のレビューや,特定の政治意識に関する語があらかじめ分かっている場合,語のグループをまとめて一つのものとして取り扱い,これらを合計して単一の特長として分析すると上手くいくかもしれません.

次の例では,テロリズムに関連する言葉や経済に関連する言葉が,クリントン以降の大統領演説コーパスでどのように異なるかを見てみます.

recentCorpus <- corpus_subset(data_corpus_inaugural, Year > 1991)

テロリズムと経済という2つのリストからなる辞書を作成します.

myDict <- dictionary(list(terror = c("terrorism", "terrorists", "threat"),
                          economy = c("jobs", "business", "grow", "work")))

文書行列を作成するときに,この辞書をdfm()dictionaryに渡します.

byPresMat <- dfm(recentCorpus, dictionary = myDict)
byPresMat
## Document-feature matrix of: 7 documents, 2 features (14.3% sparse).
## 7 x 2 sparse Matrix of class "dfm"
##               features
## docs           terror economy
##   1993-Clinton      0       8
##   1997-Clinton      1       8
##   2001-Bush         0       4
##   2005-Bush         1       6
##   2009-Obama        1      10
##   2013-Obama        1       6
##   2017-Trump        1       5

dictionary()は,LIWCやWordstatなどの一般的な辞書ファイルを読み込むことができますです.以下では,LIWCの辞書を大統領就任演説コーパスに適用しています.

liwcdict <- dictionary(file = "~/Dropbox/QUANTESS/dictionaries/LIWC/LIWC2001_English.dic",
                       format = "LIWC")
liwcdfm <- dfm(data_corpus_inaugural[52:58], dictionary = liwcdict)
liwcdfm[, 1:10]

追加の事例

文書の類似性

presDfm <- dfm(corpus_subset(data_corpus_inaugural, Year > 1980), 
               remove = stopwords("english"), stem = TRUE, remove_punct = TRUE)
obamaSimil <- textstat_simil(presDfm, c("2009-Obama" , "2013-Obama"), 
                             margin = "documents", method = "cosine")
obamaSimil
##              2009-Obama 2013-Obama
## 2009-Obama    1.0000000  0.6815711
## 2013-Obama    0.6815711  1.0000000
## 1981-Reagan   0.6229949  0.6376412
## 1985-Reagan   0.6434472  0.6629428
## 1989-Bush     0.6253944  0.5784290
## 1993-Clinton  0.6280946  0.6265428
## 1997-Clinton  0.6593018  0.6466030
## 2001-Bush     0.6018113  0.6193608
## 2005-Bush     0.5266249  0.5867178
## 2017-Trump    0.5192075  0.5160104
# dotchart(as.list(obamaSimil)$"2009-Obama", xlab = "Cosine similarity")

上記の文書間の類似性から樹形図を作成して,大統領を分類することができます.

data(data_corpus_SOTU, package = "quantedaData")
presDfm <- dfm(corpus_subset(data_corpus_SOTU, Date > as.Date("1980-01-01")), 
               stem = TRUE, remove_punct = TRUE,
               remove = stopwords("english"))
presDfm <- dfm_trim(presDfm, min_count = 5, min_docfreq = 3)
# hierarchical clustering - get distances on normalized dfm
presDistMat <- textstat_dist(dfm_weight(presDfm, "relfreq"))
# hiarchical clustering the distance object
presCluster <- hclust(presDistMat)
# label with document names
presCluster$labels <- docnames(presDfm)
# plot as a dendrogram
plot(presCluster, xlab = "", sub = "", main = "Euclidean Distance on Normalized Token Frequency")

文書間と同様に用語間の類似性も測定できます.

sim <- textstat_simil(presDfm, c("fair", "health", "terror"), method = "cosine", margin = "features")
lapply(as.list(sim), head, 10)
## $fair
##   economi     begin jefferson    author     faith      call   struggl 
## 0.9080252 0.9075951 0.8981462 0.8944272 0.8866586 0.8608285 0.8451543 
##      best     creat    courag 
## 0.8366600 0.8347300 0.8326664

文書のスケーリング

quantedaにはたくさんの計量テキスト分析のためのモデルが含まれていますが,ワードフィッシュ(textmodel_wordfish())による教師なしの文書のスケーリングを使ってみます.

# make prettier document names
ieDfm <- dfm(data_corpus_irishbudget2010)
textmodel_wordfish(ieDfm, dir = c(2, 1))
## Fitted wordfish model:
## Call:
##  textmodel_wordfish.dfm(x = ieDfm, dir = c(2, 1))
## 
## Estimated document positions:
## 
##                                Documents      theta         SE       lower
## 1        2010_BUDGET_01_Brian_Lenihan_FF  1.8209499 0.02032336  1.78111612
## 2       2010_BUDGET_02_Richard_Bruton_FG -0.5932807 0.02818846 -0.64853012
## 3         2010_BUDGET_03_Joan_Burton_LAB -1.1136790 0.01540265 -1.14386823
## 4        2010_BUDGET_04_Arthur_Morgan_SF -0.1219288 0.02846330 -0.17771690
## 5          2010_BUDGET_05_Brian_Cowen_FF  1.7724200 0.02364085  1.72608396
## 6           2010_BUDGET_06_Enda_Kenny_FG -0.7145798 0.02650268 -0.76652507
## 7      2010_BUDGET_07_Kieran_ODonnell_FG -0.4844834 0.04171492 -0.56624464
## 8       2010_BUDGET_08_Eamon_Gilmore_LAB -0.5616683 0.02967373 -0.61982877
## 9     2010_BUDGET_09_Michael_Higgins_LAB -0.9703126 0.03850567 -1.04578369
## 10       2010_BUDGET_10_Ruairi_Quinn_LAB -0.9589248 0.03892398 -1.03521585
## 11     2010_BUDGET_11_John_Gormley_Green  1.1807221 0.07221440  1.03918189
## 12       2010_BUDGET_12_Eamon_Ryan_Green  0.1866473 0.06294127  0.06328239
## 13     2010_BUDGET_13_Ciaran_Cuffe_Green  0.7421930 0.07245424  0.60018268
## 14 2010_BUDGET_14_Caoimhghin_OCaolain_SF -0.1840748 0.03666277 -0.25593382
##          upper
## 1   1.86078371
## 2  -0.53803137
## 3  -1.08348984
## 4  -0.06614078
## 5   1.81875610
## 6  -0.66263457
## 7  -0.40272216
## 8  -0.50350776
## 9  -0.89484148
## 10 -0.88263383
## 11  1.32226236
## 12  0.31001215
## 13  0.88420329
## 14 -0.11221576
## 
## Estimated feature scores: showing first 30 beta-hats for features
## 
##            when               i       presented             the 
##     -0.09921416      0.38801227      0.39878221      0.25593308 
##   supplementary          budget              to            this 
##      1.11585345      0.09914364      0.37006803      0.30692462 
##           house            last           april               , 
##      0.19905962      0.28970709     -0.09527213      0.34534182 
##            said              we           could            work 
##     -0.71932033      0.47991222     -0.52977465      0.58226316 
##             our             way         through          period 
##      0.74372383      0.33610515      0.65981764      0.55620779 
##              of          severe        economic        distress 
##      0.33931211      1.27909804      0.47866025      1.84453970 
##               .           today             can          report 
##      0.27351503      0.17418685      0.36377507      0.69175117 
##            that notwithstanding 
##      0.08832455      1.84453970

トピックモデル

convert()を用いると,dfmをtopicmodelsLDA()形式のデータに転換して,簡単にトピックモデルを適用できます.

quantdfm <- dfm(data_corpus_irishbudget2010, 
                remove_punct = TRUE, remove_numbers = TRUE, remove = stopwords("english"))
quantdfm <- dfm_trim(quantdfm, min_count = 4, max_docfreq = 10)
quantdfm
## Document-feature matrix of: 14 documents, 1,263 features (64.5% sparse).
if (require(topicmodels)) {
    myLDAfit20 <- LDA(convert(quantdfm, to = "topicmodels"), k = 20)
    get_terms(myLDAfit20, 5)
}
## Loading required package: topicmodels
##      Topic 1     Topic 2       Topic 3        Topic 4    Topic 5     
## [1,] "failed"    "kind"        "society"      "welfare"  "levy"      
## [2,] "strategy"  "imagination" "enterprising" "system"   "million"   
## [3,] "needed"    "policies"    "sense"        "taxation" "carbon"    
## [4,] "ministers" "wit"         "equal"        "fáil"     "colleagues"
## [5,] "system"    "face"        "nation"       "live"     "placing"   
##      Topic 6       Topic 7    Topic 8      Topic 9     Topic 10  
## [1,] "alternative" "fianna"   "reduction"  "fianna"    "spending"
## [2,] "citizenship" "fáil"     "million"    "prsi"      "measures"
## [3,] "wealth"      "national" "investment" "earning"   "pension" 
## [4,] "adjustment"  "irish"    "rates"      "taoiseach" "million" 
## [5,] "breaks"      "support"  "scheme"     "top"       "review"  
##      Topic 11  Topic 12  Topic 13     Topic 14    Topic 15   Topic 16   
## [1,] "welfare" "child"   "measures"   "taoiseach" "million"  "taoiseach"
## [2,] "system"  "benefit" "support"    "fine"      "welfare"  "employees"
## [3,] "parties" "today"   "investment" "gael"      "support"  "rate"     
## [4,] "child"   "welfare" "recovery"   "may"       "back"     "referred" 
## [5,] "sinn"    "per"     "action"     "irish"     "continue" "debate"   
##      Topic 17    Topic 18  Topic 19    Topic 20 
## [1,] "sustained" "benefit" "care"      "fianna" 
## [2,] "person"    "day"     "welfare"   "child"  
## [3,] "believe"   "fáil"    "per"       "taxes"  
## [4,] "raising"   "lenihan" "allowance" "earning"
## [5,] "continue"  "bank"    "hit"       "better"