［翻訳］tidytext vignette: tidytext入門


この文書は，Julia Silge と David Robinson によるRパッケージ [`tidytext`](https://cran.r-project.org/package=tidytext) (version 0.1.2) のビネット ["Introduction to tidytext"](https://cran.r-project.org/package=tidytext/vignettes/tidytext.html) の日本語訳です．

License: [MIT](https://opensource.org/licenses/MIT)

----

<!-- ### The Life-Changing Magic of Tidying Text -->

### 人生がときめくテキスト整然化の魔法

<!-- Using [tidy data principles](https://www.jstatsoft.org/article/view/v059i10) can make many text mining tasks easier, more effective, and consistent with tools already in wide use. Much of the infrastructure needed for text mining with tidy data frames already exists in packages like [dplyr](https://cran.r-project.org/package=dplyr), [broom](https://cran.r-project.org/package=broom), [tidyr](https://cran.r-project.org/package=tidyr) and [ggplot2](https://cran.r-project.org/package=ggplot2). In this package, we provide functions and supporting data sets to allow conversion of text to and from tidy formats, and to switch seamlessly between tidy tools and existing text mining packages. -->

[整然データの原則](http://id.fnshr.info/2017/01/09/trans-tidy-data/)（tidy data principles）[^tidydata]を用いると，多くのテキストマイニングのタスクをより容易で効果的に，かつ既に広く利用されているツールと整合的なやり方でこなすことができます．整然データフレームを用いてテキストマイニングを行うための基盤は，[dplyr](https://cran.r-project.org/package=dplyr)や[broom](https://cran.r-project.org/package=broom)， [tidyr](https://cran.r-project.org/package=tidyr)や[ggplot2](https://cran.r-project.org/package=ggplot2)といったパッケージの中に既に存在しています．このパッケージでは，テキストと整然形式とを相互に変換したり，整然ツールと既存のテキストマイニングパッケージをシームレスに行き来したりすることを可能にするための関数や補助的なデータセットを提供します．

[^tidydata]: 原文のリンク先は[ここ](https://www.jstatsoft.org/article/view/v059i10)．本文のリンク先は西原史暁氏による翻訳．


<!-- ### A few first tidy text mining examples -->

### まずは整然テキストマイニングの例をいくつか

<!-- The novels of Jane Austen can be so tidy! Let's use the text of Jane Austen's 6 completed, published novels from the [janeaustenr](https://cran.r-project.org/package=janeaustenr) package, and transform them into a tidy format. janeaustenr provides them as a one-row-per-line format: -->

ジェーン・オースティンの小説をとても整然としたものにできます！[janeaustenr](https://cran.r-project.org/package=janeaustenr)パッケージに収められた，ジェーン・オースティンの完結し出版された小説六編を使って，それらを整然形式に変換しましょう．janeaustenrはこれらの小説を，小説の一行をデータフレームの一行とする形式で提供しています．


```r
library(janeaustenr)
library(dplyr)
library(stringr)

original_books <- austen_books() %>%
  group_by(book) %>%
  mutate(linenumber = row_number(),
         chapter = cumsum(str_detect(text, regex("^chapter [\\divxlc]",
                                                 ignore_case = TRUE)))) %>%
  ungroup()

original_books
```

```
## # A tibble: 73,422 × 4
##                     text                book linenumber chapter
##                    <chr>              <fctr>      <int>   <int>
## 1  SENSE AND SENSIBILITY Sense & Sensibility          1       0
## 2                        Sense & Sensibility          2       0
## 3         by Jane Austen Sense & Sensibility          3       0
## 4                        Sense & Sensibility          4       0
## 5                 (1811) Sense & Sensibility          5       0
## 6                        Sense & Sensibility          6       0
## 7                        Sense & Sensibility          7       0
## 8                        Sense & Sensibility          8       0
## 9                        Sense & Sensibility          9       0
## 10             CHAPTER 1 Sense & Sensibility         10       1
## # ... with 73,412 more rows
```

<!-- To work with this as a tidy dataset, we need to restructure it as **one-token-per-row** format. The `unnest_tokens` function is a way to convert a dataframe with a text column to be one-token-per-row: -->

これを整然データセットとして取り扱うためには，**1行1トークン**の形式に再構成する必要があります．テキスト列を持つデータフレームを1行1トークン形式に変換するのが`unnest_tokens`関数です．


```r
library(tidytext)
tidy_books <- original_books %>%
  unnest_tokens(word, text)

tidy_books
```

```
## # A tibble: 725,054 × 4
##                   book linenumber chapter        word
##                 <fctr>      <int>   <int>       <chr>
## 1  Sense & Sensibility          1       0       sense
## 2  Sense & Sensibility          1       0         and
## 3  Sense & Sensibility          1       0 sensibility
## 4  Sense & Sensibility          3       0          by
## 5  Sense & Sensibility          3       0        jane
## 6  Sense & Sensibility          3       0      austen
## 7  Sense & Sensibility          5       0        1811
## 8  Sense & Sensibility         10       1     chapter
## 9  Sense & Sensibility         10       1           1
## 10 Sense & Sensibility         13       1         the
## # ... with 725,044 more rows
```

<!-- This function uses the [tokenizers package](https://github.com/lmullen/tokenizers) to separate each line into words. The default tokenizing is for words, but other options include characters, ngrams, sentences, lines, paragraphs, or separation around a regex pattern. -->

この関数は[tokenizersパッケージ](https://github.com/lmullen/tokenizers)を使って各行を単語に分割します．デフォルトではトークン化は語単位で行われますが，他のオプションとして，文字・Nグラム・文・行・段落を単位としたり，正規表現のパターンで区切ったりするものもあります．

<!-- Now that the data is in one-word-per-row format, we can manipulate it with tidy tools like dplyr. We can remove stop words (kept in the tidytext dataset `stop_words`) with an `anti_join`. -->

さて，データが一行一語形式になったので，これをdplyrのような整然ツールによって操作することができます．`anti_join`を用いれば，ストップワード（tidytextの`stop_words`データセットに入っています）を除去することができます．


```r
data("stop_words")
cleaned_books <- tidy_books %>%
  anti_join(stop_words)
```

<!-- We can also use `count` to find the most common words in all the books as a whole. -->

また，`count`を使って，すべての本を合わせた全体の中で最も出現頻度の高い単語を見つけることもできます．


```r
cleaned_books %>%
  count(word, sort = TRUE) 
```

```
## # A tibble: 13,914 × 2
##      word     n
##     <chr> <int>
## 1    miss  1855
## 2    time  1337
## 3   fanny   862
## 4    dear   822
## 5    lady   817
## 6     sir   806
## 7     day   797
## 8    emma   787
## 9  sister   727
## 10  house   699
## # ... with 13,904 more rows
```

<!-- Sentiment analysis can be done as an inner join. Three sentiment lexicons are in the tidytext package in the `sentiments` dataset. Let's look at the words with a joy score from the NRC lexicon. What are the most common joy words in *Emma*? -->

感情分析は内部結合（inner join）として実行できます．tidytextパッケージの`sentiments`データセットには，三つの感情語辞書が収められています．NRC辞書で喜び（joy）のスコアが割当てられている単語を見てみましょう．「エマ（*Emma*）」で最も出現頻度の高い喜びの単語は何でしょうか．


```r
nrcjoy <- get_sentiments("nrc") %>%
  filter(sentiment == "joy")

tidy_books %>%
  filter(book == "Emma") %>%
  semi_join(nrcjoy) %>%
  count(word, sort = TRUE)
```

```
## # A tibble: 303 × 2
##       word     n
##      <chr> <int>
## 1     good   359
## 2    young   192
## 3   friend   166
## 4     hope   143
## 5    happy   125
## 6     love   117
## 7     deal    92
## 8    found    92
## 9  present    89
## 10    kind    82
## # ... with 293 more rows
```

<!-- Or instead we could examine how sentiment changes during each novel. Let's find a sentiment score for each word using the Bing lexicon, then count the number of positive and negative words in defined sections of each novel. -->

あるいは，各小説の中で感情がどう変化してゆくのかを調べることもできるでしょう．bing辞書を使って各単語の感情スコアを調べて，それから，各小説で定義されたセクションの中のポジティブな単語とネガティブな単語の数を数えてみましょう．


```r
library(tidyr)
bing <- get_sentiments("bing")

janeaustensentiment <- tidy_books %>%
  inner_join(bing) %>%
  count(book, index = linenumber %/% 80, sentiment) %>%
  spread(sentiment, n, fill = 0) %>%
  mutate(sentiment = positive - negative)
```

<!-- Now we can plot these sentiment scores across the plot trajectory of each novel. -->

こうして，各小説の筋書きに沿って感情スコアをプロットすることができます．


```r
library(ggplot2)

ggplot(janeaustensentiment, aes(index, sentiment, fill = book)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
  facet_wrap(~book, ncol = 2, scales = "free_x")
```

![unnamed-chunk-7-1.png](https://qiita-image-store.s3.amazonaws.com/0/58917/cadfaaaa-364d-79ba-7045-40c675139d89.png)


<!-- ### Most common positive and negative words -->

### 最も出現頻度の高いポジティブ単語とネガティブ単語

<!-- One advantage of having the data frame with both sentiment and word is that we can analyze word counts that contribute to each sentiment. -->

感情と単語が両方入ったデータフレームを持つことの利点は，それぞれの感情に寄与している単語の出現回数を分析できることです．


```r
bing_word_counts <- tidy_books %>%
  inner_join(bing) %>%
  count(word, sentiment, sort = TRUE) %>%
  ungroup()

bing_word_counts
```

```
## # A tibble: 2,585 × 3
##        word sentiment     n
##       <chr>     <chr> <int>
## 1      miss  negative  1855
## 2      well  positive  1523
## 3      good  positive  1380
## 4     great  positive   981
## 5      like  positive   725
## 6    better  positive   639
## 7    enough  positive   613
## 8     happy  positive   534
## 9      love  positive   495
## 10 pleasure  positive   462
## # ... with 2,575 more rows
```

<!-- This can be shown visually, and we can pipe straight into ggplot2 because of the way we are consistently using tools built for handling tidy data frames. -->

これは視覚的に示すことができます．われわれは整然データフレームを扱うために作られたツールを首尾一貫して使用しているので，ggplot2までパイプを直接つないでいくことができます．


```r
bing_word_counts %>%
  filter(n > 150) %>%
  mutate(n = ifelse(sentiment == "negative", -n, n)) %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(word, n, fill = sentiment)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
  ylab("Contribution to sentiment")
```

![unnamed-chunk-9-1.png](https://qiita-image-store.s3.amazonaws.com/0/58917/e6272030-53ba-1b4f-afd3-cc18006083c9.png)


<!-- This lets us spot an anomaly in the sentiment analysis; the word "miss" is coded as negative but it is used as a title for young, unmarried women in Jane Austen's works. If it were appropriate for our purposes, we could easily add "miss" to a custom stop-words list using `bind_rows`. -->

こうすると，この感情分析における変則的な例が見つかります．「miss」という単語がネガティブなものとしてコーディングされていますが，この単語は，ジェーン・オースティンの作品では若い未婚の女性に対する敬称として使われているのです．もしわれわれの目的にとって適切であれば，`bind_rows`を使って「miss」をカスタムしたストップワードリストに簡単に加えることができます．

<!-- ### Wordclouds -->

### ワードクラウド

<!-- We've seen that this tidy text mining approach works well with ggplot2, but having our data in a tidy format is useful for other plots as well. -->

整然テキストマイニングの方法が，gpplot2と組み合わせるとうまくいくのを見てきました．しかしデータを整然形式で持っておくのは，他の作図においても同じように有用です．

<!-- For example, consider the wordcloud package. Let's look at the most common words in Jane Austen's works as a whole again. -->

例としてwordcloudパッケージを考えます．ジェーン・オースティンの作品全体で最も出現頻度の高い単語を再び見てみましょう．


```r
library(wordcloud)

cleaned_books %>%
  count(word) %>%
  with(wordcloud(word, n, max.words = 100))
```

![unnamed-chunk-10-1.png](https://qiita-image-store.s3.amazonaws.com/0/58917/12099bed-47c2-dbbe-3131-af25101fa07f.png)


<!-- In other functions, such as `comparison.cloud`, you may need to turn it into a matrix with reshape2's `acast`. Let's do the sentiment analysis to tag positive and negative words using an inner join, then find the most common positive and negative words. Until the step where we need to send the data to `comparison.cloud`, this can all be done with joins, piping, and dplyr because our data is in tidy format. -->

`comparison.cloud`のような他の関数では，reshape2の`acast`でデータを行列に変換する必要があるかもしれません．内部結合を用いてポジティブな単語とネガティブな単語にタグ付けし，それから最も出現頻度の高いポジティブな単語とネガティブな単語を見つける感情分析を行ってみましょう．データが整然形式になっているので，データを`comparison.cloud`に送る段階までは，すべて結合・パイプとdplyrによって実行できます．


```r
library(reshape2)

tidy_books %>%
  inner_join(bing) %>%
  count(word, sentiment, sort = TRUE) %>%
  acast(word ~ sentiment, value.var = "n", fill = 0) %>%
  comparison.cloud(colors = c("#F8766D", "#00BFC4"),
                   max.words = 100)
```

![wordcloud-1.png](https://qiita-image-store.s3.amazonaws.com/0/58917/82129db0-9f40-85b5-9d27-af9797b98ed0.png)

<!-- ### Looking at units beyond just words -->

### 単語より長い単位を見る

<!-- Lots of useful work can be done by tokenizing at the word level, but sometimes it is useful or necessary to look at different units of text. For example, some sentiment analysis algorithms look beyond only unigrams (i.e. single words) to try to understand the sentiment of a sentence as a whole. These algorithms try to understand that -->

単語レベルのトークン化によって，多くの役立つ仕事ができますが，時にはテキストを異なった単位で見ることが有用だったり必要だったりすることもあります．たとえば，感情分析アルゴリズムの中には，ユニグラム（単語）を見るだけでなく，文全体の感情を理解しようとするものがあります．こういったアルゴリズムは

> I am not having a good day. （私はよい一日を過ごしていない．）

<!-- is a sad sentence, not a happy one, because of negation. The [Stanford CoreNLP](http://stanfordnlp.github.io/CoreNLP/) tools and the [sentimentr R package](https://github.com/trinker/sentimentr) (currently available on Github but not CRAN) are examples of such sentiment analysis algorithms. For these, we may want to tokenize text into sentences. -->

という文が，否定が入っているために，喜びではなくて悲しみを表していることを理解しようとします．[Stanford CoreNLP](http://stanfordnlp.github.io/CoreNLP/)のツールや[sentimentr R パッケージ](https://github.com/trinker/sentimentr)（今のところCRANにはないですがGithubで利用可能です）は，そのような感情分析アルゴリズムの例です．これらを使うために，テキストを文単位にトークン化したくなることがあるかもしれません．


```r
PandP_sentences <- data_frame(text = prideprejudice) %>% 
  unnest_tokens(sentence, text, token = "sentences")
```

<!-- Let's look at just one. -->

一文だけ見てみましょう．


```r
PandP_sentences$sentence[2]
```

```
## [1] "however little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered the rightful property of some one or other of their daughters."
```

<!-- The sentence tokenizing does seem to have a bit of trouble with UTF-8 encoded text, especially with sections of dialogue; it does much better with punctuation in ASCII. -->

エンコーディングがUTF-8のテキストでは，文単位のトークン化に少々問題があるようです．特に対話のセクションで問題があるようです．ASCIIの句読点ではもっとうまくいきます．

<!-- Another option in `unnest_tokens` is to split into tokens using a regex pattern. We could use this, for example, to split the text of Jane Austen's novels into a data frame by chapter. -->

`unnest_tokens`のもう一つのオプションとして，正規表現のパターンを用いてトークンに分割するというものがあります．これを使って，たとえばジェーン・オースティンの小説のテキストを，章ごとに分割したデータフレームにすることができるでしょう．


```r
austen_chapters <- austen_books() %>%
  group_by(book) %>%
  unnest_tokens(chapter, text, token = "regex", pattern = "Chapter|CHAPTER [\\dIVXLC]") %>%
  ungroup()

austen_chapters %>% 
  group_by(book) %>% 
  summarise(chapters = n())
```

```
## # A tibble: 6 × 2
##                  book chapters
##                <fctr>    <int>
## 1 Sense & Sensibility       51
## 2   Pride & Prejudice       62
## 3      Mansfield Park       49
## 4                Emma       56
## 5    Northanger Abbey       32
## 6          Persuasion       25
```

<!-- We have recovered the correct number of chapters in each novel (plus an "extra" row for each novel title). In this data frame, each row corresponds to one chapter. -->

各小説の章の数が正しく再現されました（小説の題名を含む「余分な」一行が付いていますが）．このデータフレームでは，各行が各章に対応しています．

<!-- Near the beginning of this vignette, we used a similar regex to find where all the chapters were in Austen's novels for a tidy data frame organized by one-word-per-row. We can use tidy text analysis to ask questions such as what are the most negative chapters in each of Jane Austen's novels? First, let's get the list of negative words from the Bing lexicon. Second, let's make a dataframe of how many words are in each chapter so we can normalize for the length of chapters. Then, let's find the number of negative words in each chapter and divide by the total words in each chapter. Which chapter has the highest proportion of negative words? -->

このビネットの最初の方で，一行一語形式に整理した整然データフレームに対して，オースティンの小説の章を特定するために似たような正規表現を使いました．整然テキスト分析を使って，ジェーン・オースティンの各小説において最もネガティブな章はどれか，といった疑問に答えることができます．まず，Bing辞書からネガティブな単語のリストを取得します．次に，章の長さで正規化ができるように，各章の単語数を持つデータフレームを作ります．それから，各章のネガティブな単語の数を特定して，章の全単語数で割ります．ネガティブな単語の割合が最も高いのはどの章でしょうか．


```r
bingnegative <- get_sentiments("bing") %>%
  filter(sentiment == "negative")

wordcounts <- tidy_books %>%
  group_by(book, chapter) %>%
  summarize(words = n())

tidy_books %>%
    semi_join(bingnegative) %>%
  group_by(book, chapter) %>%
  summarize(negativewords = n()) %>%
  left_join(wordcounts, by = c("book", "chapter")) %>%
  mutate(ratio = negativewords/words) %>%
  filter(chapter != 0) %>%
  top_n(1)
```

```
## Source: local data frame [6 x 5]
## Groups: book [6]
## 
##                  book chapter negativewords words      ratio
##                <fctr>   <int>         <int> <int>      <dbl>
## 1 Sense & Sensibility      43           161  3405 0.04728341
## 2   Pride & Prejudice      34           111  2104 0.05275665
## 3      Mansfield Park      46           173  3685 0.04694708
## 4                Emma      15           151  3340 0.04520958
## 5    Northanger Abbey      21           149  2982 0.04996647
## 6          Persuasion       4            62  1807 0.03431101
```

<!-- These are the chapters with the most negative words in each book, normalized for number of words in the chapter. What is happening in these chapters? In Chapter 43 of *Sense and Sensibility* Marianne is seriously ill, near death, and in Chapter 34 of *Pride and Prejudice* Mr. Darcy proposes for the first time (so badly!). Chapter 46 of *Mansfield Park* is almost the end, when everyone learns of Henry's scandalous adultery, Chapter 15 of *Emma* is when horrifying Mr. Elton proposes, and in Chapter 21 of *Northanger Abbey* Catherine is deep in her Gothic faux fantasy of murder, etc. Chapter 4 of *Persuasion* is when the reader gets the full flashback of Anne refusing Captain Wentworth and how sad she was and what a terrible mistake she realized it to be. -->

これが，それぞれの本において，章の単語数で正規化したときに最もネガティブな単語が多い章です．これらの章では何が起こっているのでしょうか．「分別と多感（*Sense and Sensibility*）」の43章では，マリアンが深刻な病気で瀕死となっており，「高慢と偏見（*Pride and Prejudice*」の34章は，ダーシー氏が最初の（ひどい）求婚を行うところです．「マンスフィールド・パーク（*Mansfield Park*）」の46章はほとんど終わりに近く，ヘンリーの恥ずべき不貞が発覚するところで，「エマ（*Emma*」の15章は恐ろしいエルトン氏が求婚するところです．「ノーサンガー・アビー（*Northanger Abbey*）」の21章ではキャサリンがゴシック小説の殺人の空想にふけったりしています．「説得（*Persuation*）」の4章は，アンがウェントワース大佐を拒否してどんなに悲しく，ひどい誤りを犯したかを悟る一部始終の回想シーンを読者が読むところです．