非分析者にテキストデータ分析の結果を説明する可視化のコツ

# はじめに

データ分析というものは、質が担保されて、かつデータ分析の専門的な知識を持っていない人に理解されることによって、初めて価値を発揮する。アカデミアの世界でもビジネスの世界でも、いわゆる非分析者に分析結果を説明する必要がある。そこで、本記事では、私が執筆した下記の記事の内容を軸に、テキストデータ分析をわかりやすく可視化する方法を説明する。データの前処理などは最近の業務で得た知見をもとに若干変更しているが、前提の説明や手法の紹介、参考文献などは前回の記事からご確認ください。

https://qiita.com/Gotoubun_taiwan/items/777f537c315cd9e59757

# 分析

## モデル推定

分析のコードについては、下のコードでは前回の記事から大きな変更はないが、htmlのタグが残ることがわかったので、一旦全てのローマ字を消すことにした。

```r
library(RMeCab)
library(tidyverse)
library(quanteda)
library(distrom)
url  <- 'https://s3.amazonaws.com/amazon-reviews-pds/tsv/amazon_reviews_multilingual_JP_v1_00.tsv.gz'
amazon_df <- read_tsv(url)


review_mecabbed <- c()

for (i in 1:nrow(amazon_df)){
  # 正規表現を[^一-龠ぁ-んーァ-ヶーa-zA-Z]から[^一-龠ぁ-んーァ-ヶー]に変更
  this_review <- str_replace_all(amazon_df$review_body[i], "[^一-龠ぁ-んーァ-ヶー]", " ")
  mecab_output <- unlist(RMeCabC(this_review, 1))
  review_mecabbed[i] <- str_c(mecab_output[which(names(mecab_output) == "名詞")], collapse = " ")
}
```

文書行列を作成するところだが、最近は業務でuni-gram、bi-gram、tri-gramを全て入れた方が、解釈可能性の高い良質の結果が得られることがわかったので、次のようにコードを変更した。

```r
review_dfm <- review_mecabbed %>%
  phrase() %>%
  tokens() %>%
  tokens_ngrams(1:3) %>%
  dfm() %>%
  dfm_trim(min_termfreq = 500, max_termfreq = 100000)
```

では早速残りの処理をして、モデルの推定を行う。

```r
covars <- model.matrix(
  ~ - 1 + product_category + star_rating + helpful_votes, data = amazon_df
)

set.seed(12345)
train_id <- sample(
  1:nrow(amazon_df),
  round(nrow(amazon_df) * 0.7),
  replace = FALSE
)


cl <- makeCluster(16)

fits <- dmr(cl,
            counts = review_dfm[train_id,],
            covars = covars[train_id,],
            varweight = c(
              rep(1/20, sum(str_detect(colnames(covars), "product_category"))),
              rep(1, 2)
            ),
            verb = 1
)

stopCluster(cl)

B <- coef(fits)
```

## 可視化

前回の記事では、直接単語の係数をプリントすることで結果を確認したが、非分析者にとって係数という概念は分かりにくいので、係数の大きさに応じて単語をワードクラウドの形で可視化できるwordcloud2というパッケージの利用方法を説明する。

```r
library(wordcloud2)
```
wordcloud2の概念は簡単で、データフレイムの形で単語とその（正の）ウエイトを渡せば直接ワードクラウドにしてくれる。具体的には以下のようなデータ形式を渡せば問題ない。

```r
> tibble(
    term = names(sort(B["star_rating",], decreasing = TRUE)),
    freq = as.numeric(sort(B["star_rating",], decreasing = TRUE))
)
# A tibble: 3,777 × 2
   term       freq
   <chr>     <dbl>
 1 方_是非   0.615
 2 感激      0.610
 3 是非      0.561
 4 虜        0.541
 5 マリア    0.510
 6 中_最高   0.498
 7 期待_以上 0.497
 8 圧倒      0.476
 9 脱帽      0.465
10 大変_満足 0.460
# … with 3,767 more rows
```
では早速wordcloud2を使って、高レビュー数を識別する単語を可視化しよう。

```r
tibble(
  term = names(sort(B["star_rating",], decreasing = TRUE)),
  freq = as.numeric(sort(B["star_rating",], decreasing = TRUE))
) %>%
  wordcloud2()
```

![good word.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/8a1e586a-6e18-c068-0b5f-c429f6d821f5.png)

非分析者に説明する際は、このワードクラウドは単語の出現頻度ではなく、単語の「高レビュー数」に対する寄与度の高さですよと強調した方が誤解が生じにくい。

次に低レビューを識別する単語を出すが、上述したように、wordcloud2は正のウエイトしか許容しないため、マイナス記号がついているはずの低レビューを識別する単語の係数にマイナスをつけてプラスにする必要がある。

```r
tibble(
  term = names(sort(B["star_rating",], decreasing = FALSE)),
  #低レビューを識別する単語はマイナスになるため、マイナスをつけて正の値に変換する
  freq = -as.numeric(sort(B["star_rating",], decreasing = FALSE))
) %>%
  wordcloud2()
```

![bad words.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/44224a61-a546-bb3c-faac-818a607be348.png)

このように、問題なく多項逆回帰モデルの単語の係数をワードクラウドの形で可視化したが、モデルの設定によって大量のワードクラウドがある場合、ワードクラウドを一枚一枚作るのではなく、ダッシュボードを作成して非分析者に分析結果と対話させた方が良いかもしれない。Rでダッシュボードを作成する方法はまた別の記事で紹介する。