1  gibasaの基本的な使い方

1.1 テキストデータ

ここでは、audubonパッケージに含まれているaudubon::polanoというデータを例にgibasaの基本的な使い方を説明していきます。このデータは、青空文庫で公開されている、宮沢賢治の「ポラーノの広場」という小説を、改行ごとにひとつの要素としてベクトルにしたものです。

このデータを、次のようなかたちのデータフレーム(tibble)にします。

dat_txt <-
  tibble::tibble(
    doc_id = seq_along(audubon::polano) |> as.character(),
    text = audubon::polano
  ) |>
  dplyr::mutate(text = audubon::strj_normalize(text))

str(dat_txt)
#> tibble [899 × 2] (S3: tbl_df/tbl/data.frame)
#>  $ doc_id: chr [1:899] "1" "2" "3" "4" ...
#>  $ text  : chr [1:899] "ポラーノの広場" "宮沢賢治" "前十七等官レオーノ・キュースト誌" "宮沢賢治訳述" ...

このかたちのデータフレームは、Text Interchange Formats(TIF)という仕様を念頭においている形式です(ちなみに、このかたちのデータフレームはreadtextパッケージを使うと簡単に得ることができますが、readtextクラスのオブジェクトはdplyrと相性が悪いようなので、使う場合はdplyr::tibbleなどでtibbleにしてしまうことをおすすめします)。

Text Interchange Formats(TIF)は、2017年にrOpenSci Text Workshopで整備された、テキスト分析用のRパッケージのデザインパターンのようなものです。

TIFでは、コーパス(corpus)、文書単語行列(dtm)、トークン(token)という3種類のオブジェクトの形式が定義されており、異なるパッケージ間で同様の形式を扱うようにすることで、複数のパッケージを通じて便利にテキスト分析を進められるようになっています。

上のdat_txtは、文書の集合であるコーパスをデータフレームのかたちで保持したものです。この形式のデータフレームは、次のように、tidytexttokenizersの関数にそのまま渡すことができます。なお、これらの形式のオブジェクトは、TIFの枠組みのなかではトークンと呼ばれます。

dat_txt |>
  tidytext::unnest_tokens(token, text) |>
  head(4)
#> # A tibble: 4 × 2
#>   doc_id token   
#>   <chr>  <chr>   
#> 1 1      ポラーノ
#> 2 1      の      
#> 3 1      広場    
#> 4 2      宮沢

dat_txt |>
  tokenizers::tokenize_words() |>
  head(4)
#> $`1`
#> [1] "ポラーノ" "の"       "広場"    
#> 
#> $`2`
#> [1] "宮沢" "賢治"
#> 
#> $`3`
#>  [1] "前"     "十七"   "等"     "官"     "レ"     "オー"   "ノ"     "キュー"
#>  [9] "スト"   "誌"    
#> 
#> $`4`
#> [1] "宮沢" "賢治" "訳述"

1.2 gibasaの使い方

1.2.1 tokenize

前節で見たように、tokenizers::tokenize_wordsやこれを利用しているtidytext::unnest_tokensは、日本語のテキストであっても機械的にトークンのかたちに整形する(分かち書きする)ことができます。

tokenizersパッケージの分かち書きは、内部的には、ICUのBoundary Analysisによるものです。この単語境界判定は、たとえば新聞記事のような、比較的整った文体の文章ではおおむね期待通り分かち書きがおこなわれ、また、日本語と英語などが混ざっている文章であってもとくに気にすることなく、高速に分かち書きできるという強みがあります。

しかし、手元にある辞書に収録されている語の通りに分かち書きしたい場合や、品詞情報などがほしい場合には、やはりMeCabのような形態素解析器による分かち書きが便利なこともあります。

gibasaは、そのようなケースにおいて、tidytext::unnest_tokensの代わりに使用できる機能を提供するために開発しているパッケージです。この機能はgibasa::tokenizeという関数として提供していて、次のように使うことができます。

dat <- gibasa::tokenize(dat_txt, text, doc_id)
str(dat)
#> tibble [26,849 × 5] (S3: tbl_df/tbl/data.frame)
#>  $ doc_id     : Factor w/ 899 levels "1","2","3","4",..: 1 1 1 2 2 3 3 3 3 3 ...
#>  $ sentence_id: int [1:26849] 1 1 1 2 2 3 3 3 3 3 ...
#>  $ token_id   : int [1:26849] 1 2 3 1 2 1 2 3 4 5 ...
#>  $ token      : chr [1:26849] "ポラーノ" "の" "広場" "宮沢" ...
#>  $ feature    : chr [1:26849] "名詞,一般,*,*,*,*,*" "助詞,連体化,*,*,*,*,の,ノ,ノ" "名詞,一般,*,*,*,*,広場,ヒロバ,ヒロバ" "名詞,固有名詞,人名,姓,*,*,宮沢,ミヤザワ,ミヤザワ" ...

1.2.2 prettify

gibasa::tokenizeの戻り値のデータフレームは、それぞれのトークンについて、MeCabから返される素性情報のすべてを含んでいるfeatureという列を持っています。

MeCabから返される素性情報は、使用している辞書によって異なります。たとえば、IPA辞書やUniDic(2.1.2, aka unidic-lite)の素性は、次のような情報を持っています。

gibasa::get_dict_features("ipa")
#> [1] "POS1"        "POS2"        "POS3"        "POS4"        "X5StageUse1"
#> [6] "X5StageUse2" "Original"    "Yomi1"       "Yomi2"
gibasa::get_dict_features("unidic26")
#>  [1] "POS1"      "POS2"      "POS3"      "POS4"      "cType"     "cForm"    
#>  [7] "lForm"     "lemma"     "orth"      "pron"      "orthBase"  "pronBase" 
#> [13] "goshu"     "iType"     "iForm"     "fType"     "fForm"     "kana"     
#> [19] "kanaBase"  "form"      "formBase"  "iConType"  "fConType"  "aType"    
#> [25] "aConType"  "aModeType"

こうした素性情報をデータフレームの列にパースするには、gibasa::prettifyという関数を利用できます。

デフォルトではすべての素性についてパースしますが、col_select引数に残したい列名を指定することにより、特定の素性情報だけをパースすることもできます。このかたちのデータフレームは、解析するテキストの文章量によっては、数十万から数百万くらいの行からなることもよくあります。そのような規模のデータフレームについて、いちいちすべての素性をパースしていると、それだけでメモリを余計に消費してしまいます。メモリの消費を抑えるためにも、なるべく後で必要な素性だけをこまめに指定することをおすすめします。

str(gibasa::prettify(dat))
#> tibble [26,849 × 13] (S3: tbl_df/tbl/data.frame)
#>  $ doc_id     : Factor w/ 899 levels "1","2","3","4",..: 1 1 1 2 2 3 3 3 3 3 ...
#>  $ sentence_id: int [1:26849] 1 1 1 2 2 3 3 3 3 3 ...
#>  $ token_id   : int [1:26849] 1 2 3 1 2 1 2 3 4 5 ...
#>  $ token      : chr [1:26849] "ポラーノ" "の" "広場" "宮沢" ...
#>  $ POS1       : chr [1:26849] "名詞" "助詞" "名詞" "名詞" ...
#>  $ POS2       : chr [1:26849] "一般" "連体化" "一般" "固有名詞" ...
#>  $ POS3       : chr [1:26849] NA NA NA "人名" ...
#>  $ POS4       : chr [1:26849] NA NA NA "姓" ...
#>  $ X5StageUse1: chr [1:26849] NA NA NA NA ...
#>  $ X5StageUse2: chr [1:26849] NA NA NA NA ...
#>  $ Original   : chr [1:26849] NA "の" "広場" "宮沢" ...
#>  $ Yomi1      : chr [1:26849] NA "ノ" "ヒロバ" "ミヤザワ" ...
#>  $ Yomi2      : chr [1:26849] NA "ノ" "ヒロバ" "ミヤザワ" ...
str(gibasa::prettify(dat, col_select = c(1, 2)))
#> tibble [26,849 × 6] (S3: tbl_df/tbl/data.frame)
#>  $ doc_id     : Factor w/ 899 levels "1","2","3","4",..: 1 1 1 2 2 3 3 3 3 3 ...
#>  $ sentence_id: int [1:26849] 1 1 1 2 2 3 3 3 3 3 ...
#>  $ token_id   : int [1:26849] 1 2 3 1 2 1 2 3 4 5 ...
#>  $ token      : chr [1:26849] "ポラーノ" "の" "広場" "宮沢" ...
#>  $ POS1       : chr [1:26849] "名詞" "助詞" "名詞" "名詞" ...
#>  $ POS2       : chr [1:26849] "一般" "連体化" "一般" "固有名詞" ...
str(gibasa::prettify(dat, col_select = c("POS1", "Original")))
#> tibble [26,849 × 6] (S3: tbl_df/tbl/data.frame)
#>  $ doc_id     : Factor w/ 899 levels "1","2","3","4",..: 1 1 1 2 2 3 3 3 3 3 ...
#>  $ sentence_id: int [1:26849] 1 1 1 2 2 3 3 3 3 3 ...
#>  $ token_id   : int [1:26849] 1 2 3 1 2 1 2 3 4 5 ...
#>  $ token      : chr [1:26849] "ポラーノ" "の" "広場" "宮沢" ...
#>  $ POS1       : chr [1:26849] "名詞" "助詞" "名詞" "名詞" ...
#>  $ Original   : chr [1:26849] NA "の" "広場" "宮沢" ...

1.2.3 pack

gibasa::packという関数を使うと、トークンの形式のデータフレームから、各トークンを半角スペースで区切ったコーパスの形式のデータフレームにすることができます。

dat_corpus <- dat |>
  gibasa::pack()

str(dat_corpus)
#> tibble [899 × 2] (S3: tbl_df/tbl/data.frame)
#>  $ doc_id: Factor w/ 899 levels "1","2","3","4",..: 1 2 3 4 5 6 7 8 9 10 ...
#>  $ text  : chr [1:899] "ポラーノ の 広場" "宮沢 賢治" "前 十 七 等 官 レオーノ・キュースト 誌" "宮沢 賢治 訳述" ...

このかたちのデータフレームはTIFに準拠していたため、他のパッケージと組み合わせて使うのに便利なことがあります。たとえば、このかたちから、次のようにtidytext::unnest_tokensと組み合わせて、もう一度トークンの形式のデータフレームに戻すことができます。

dat_corpus |>
  tidytext::unnest_tokens(token, text, token = \(x) {
    strsplit(x, " +")
  }) |>
  head(4)
#> # A tibble: 4 × 2
#>   doc_id token   
#>   <fct>  <chr>   
#> 1 1      ポラーノ
#> 2 1      の      
#> 3 1      広場    
#> 4 2      宮沢

あるいは、次のようにquantedaと組み合わせて使うこともできます。

dat_corpus |>
  quanteda::corpus() |>
  quanteda::tokens(what = "fastestword", remove_punct = FALSE)
#> Tokens consisting of 899 documents.
#> 1 :
#> [1] "ポラーノ" "の"       "広場"    
#> 
#> 2 :
#> [1] "宮沢" "賢治"
#> 
#> 3 :
#> [1] "前"                   "十"                   "七"                  
#> [4] "等"                   "官"                   "レオーノ・キュースト"
#> [7] "誌"                  
#> 
#> 4 :
#> [1] "宮沢" "賢治" "訳述"
#> 
#> 5 :
#>  [1] "その"     "ころ"     "わたくし" "は"       "、"       "モリーオ"
#>  [7] "市"       "の"       "博物"     "局"       "に"       "勤め"    
#> [ ... and 5 more ]
#> 
#> 6 :
#>  [1] "十"   "八"   "等"   "官"   "でし" "た"   "から" "役所" "の"   "なか"
#> [11] "でも" "、"  
#> [ ... and 219 more ]
#> 
#> [ reached max_ndoc ... 893 more documents ]

1.2.4 lazy_dtなどと組み合わせて使う場合

gibasa::prettifyはデータフレームにしか使えないため、data.tableなどと組み合わせて使う場合にはtidyr::separateを代わりに使ってください。

dat_toks <- dat |>
  dtplyr::lazy_dt() |>
  tidyr::separate(feature,
    into = gibasa::get_dict_features(),
    sep = ",", extra = "merge", fill = "right"
  ) |>
  dplyr::mutate(
    token = dplyr::if_else(Original == "*", token, Original),
    token = stringr::str_c(token, POS1, POS2, sep = "/")
  ) |>
  dplyr::select(doc_id, sentence_id, token_id, token) |>
  dplyr::as_tibble() |>
  dplyr::mutate(across(where(is.character), ~ dplyr::na_if(., "*")))

str(dat_toks)
#> tibble [26,849 × 4] (S3: tbl_df/tbl/data.frame)
#>  $ doc_id     : Factor w/ 899 levels "1","2","3","4",..: 1 1 1 2 2 3 3 3 3 3 ...
#>  $ sentence_id: int [1:26849] 1 1 1 2 2 3 3 3 3 3 ...
#>  $ token_id   : int [1:26849] 1 2 3 1 2 1 2 3 4 5 ...
#>  $ token      : chr [1:26849] "ポラーノ/名詞/一般" "の/助詞/連体化" "広場/名詞/一般" "宮沢/名詞/固有名詞" ...