計量経済学をどんどんデプロイせよ！テキストデータ分析結果をダッシュボードアプリにまとめる方法

# はじめに

本記事は、前回の記事の続きで

https://qiita.com/Gotoubun_taiwan/items/9af19c9cd1713bfc44e5

パラメーターが大量に存在する統計学・機械学習モデルの結果をビジネスサイドやエンジニアなどの非分析者に説明する際に利用するダッシュボードを作成する方法の紹介が主なテーマである。

ただ、私の経験からすると、パラメーターが多すぎると、分析者自身のためも結果の全体像をパラメーターの数値だけで把握するのは不可能に近い。なので、本記事の内容は分析者自身のためのダッシュボード作成の参考としても活用できる。

# 分析

## モデル推定

前回の記事と全く同じ内容なのでそのままコードを貼る。詳細は前回の記事やもっと前の記事

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

を参考してください。

```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 = " ")
}

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

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)

```


## 結果の確認

前回の記事すでに高レビュー数と低レビュー数を識別する単語をそれぞれ可視化したので、今回は「役に立った」の数の多さと低さを識別する単語を可視化から始める。

```r
library(wordcloud2)


tibble(
  term = names(sort(B["helpful_votes",], decreasing = TRUE)),
  freq = as.numeric(sort(B["helpful_votes",], decreasing = TRUE))
) %>%
  wordcloud2()
```
![helpful words.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/fecc09ab-74b6-7f2a-6935-fcab18ccfa5c.png)

「役に立った」の数の多さを識別する単語はかなり音楽系に寄っているが、おおむね内容の詳細が多いと言えよう。

次に「役に立った」の数の低さを識別する単語を確認する。

```r
tibble(
  term = names(sort(B["helpful_votes",], decreasing = FALSE)),
  freq = -as.numeric(sort(B["helpful_votes",], decreasing = FALSE))
) %>%
  wordcloud2()
```
![not helpful.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/d064b9df-274f-1578-b73b-bc8f88c5f547.png)

感想を表す形容詞が多く、確かにこれだとなかなか「役に立つ」内容とは言えない。

また、ある商品カテゴリーに属していることを示す単語とあるカテゴリーに属していないことを示す単語も可視化できる。

具体的には「Baby」の商品カテゴリーに対するレビューであることを示す単語を確認する。

```r
tibble(
  term = names(sort(B["product_categoryBaby",], decreasing = TRUE)),
  freq = as.numeric(sort(B["product_categoryBaby",], decreasing = TRUE))
) %>%
  wordcloud2()
```
![baby.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/49cd5502-2e00-232a-46af-4924a55fff68.png)

きちんと「笑顔」、「おもちゃ」、「夫婦」などの赤ちゃんを示す単語が出ている。

「Baby」の商品カテゴリーに対するレビューではないことを示す単語というと：

```r
tibble(
  term = names(sort(B["product_categoryBaby",], decreasing = FALSE)),
  freq = -as.numeric(sort(B["product_categoryBaby",], decreasing = FALSE))
) %>%
  wordcloud2()
```
![not baby.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/cce604c1-1263-6b0f-278a-729c35cf2e0c.png)

確かに赤ちゃんと関係ない単語になっている。

このように、逆回帰モデルからさまざまな特性を識別する単語を可視化できる。具体的には下記の通りで

```r
> row.names(B)
 [1] "intercept"                              "product_categoryApparel"               
 [3] "product_categoryAutomotive"             "product_categoryBaby"                  
 [5] "product_categoryBooks"                  "product_categoryCamera"                
 [7] "product_categoryDigital_Ebook_Purchase" "product_categoryDigital_Music_Purchase"
 [9] "product_categoryDigital_Video_Download" "product_categoryElectronics"           
[11] "product_categoryHealth & Personal Care" "product_categoryHome"                  
[13] "product_categoryHome Entertainment"     "product_categoryHome Improvement"      
[15] "product_categoryKitchen"                "product_categoryLawn and Garden"       
[17] "product_categoryMobile_Apps"            "product_categoryMusic"                 
[19] "product_categoryMusical Instruments"    "product_categoryOffice Products"       
[21] "product_categoryPC"                     "product_categoryPet Products"          
[23] "product_categoryShoes"                  "product_categorySoftware"              
[25] "product_categorySports"                 "product_categoryToys"                  
[27] "product_categoryVideo"                  "product_categoryVideo DVD"             
[29] "product_categoryVideo Games"            "product_categoryWatches"               
[31] "product_categoryWireless"               "star_rating"                           
[33] "helpful_votes"            
```
切片を除けば32種類を確認できる。

32種類の変数を識別する単語のプラス（テゴリー変数の場合は当該のカテゴリーに属していることを示し、star_ratingとhelpful_votesなどの数値の場合は高さを示す）とマイナス（テゴリー変数の場合は当該のカテゴリーに属していないことを示し、star_ratingとhelpful_votesなどの数値の場合は低さを示す）を全て表示しようとすると、64枚の図ができて、for文でも回すのはかなりめんどくさい。

そこで、ダッシュボードを作成して、より自由に分析結果を表示する方法を紹介する。


# ダッシュボード作成

ここではShinyで逆回帰の結果をまとめるダッシュボードを作成する方法を紹介する。

内容は主に下記の電子書籍を参考にしている。

https://mastering-shiny.org/action-layout.html

## Shinyアプリの全体設計

ダッシュボードの全体的な設計方針としては、ユーザーが下記の2点を選ぶようにする。
```
１、見たい変数
２、その変数である・この変数の多さ強さ（positive）を識別する単語かその変数でない・この変数の少なさ弱さ（positive）を識別する単語
```

## Shinyの書き方の基礎の紹介

Shinyは簡単にいうとアプリ・ダッシュボードの全体的なUIを示すUIの部分と、実際の処理を行うSERVERの部分がある。

UI部分のXXXInput系の関数でSERVERにデータを渡し、XXXOutput系の関数でSERVERが処理した結果を受け取る構造である。

UI部分のXXXInput系の関数の第一引数はSERVERに渡す際の変数名で、たとえば第一引数が「"apple"」だったら、SERVER側でinput$appleで呼び出せる。

UI部分のXXXOutput系の関数の第一引数もSERVER側の変数名で、たとえば第一引数が「"cheese"」だったら、SERVER側でoutput$cheeseでやり取りできる。

Shinyの仕組みの詳細についてはまた別記事で紹介する。

ここで、ワードクラウドを作成するためのコードを三つピックアップして構造を再確認すると

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

tibble(
  term = names(sort(B["helpful_votes",], decreasing = FALSE)),
  freq = -as.numeric(sort(B["helpful_votes",], decreasing = FALSE))
) %>%
  wordcloud2()

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

当たり前のことであるが、見たい変数名をB["XXX",]のように渡すだけなので、この処理をSERVERに任せれば変数名を差し替える工程を自動化できる。また、見たい変数であることを示す（positive）図と見たい変数ではないことを示す（negative）図の2パターンがあるが、逆にいうと2パターンしかないので、if文で対応できる。

具体的には以下のコードになる。UI部分で定義したtarget_variableが見たい変数で、perspectiveはpositiveかnegativeの選択結果を格納するための変数である。

```r
library(shiny)

ui <- fluidPage(
  titlePanel(h1("Amazon逆回帰結果の可視化")),
  sidebarLayout(
    sidebarPanel(
      #Bの一行目は切片なので外す
      selectInput("target_variable", label = "変数", choices = row.names(B)[-1]),
      selectInput("perspective", label = "ポジティブかネガティブ", choices = c("positive", "negative"))
    ),
    mainPanel(
      wordcloud2Output("plot")
    )
  )
)

server <- function(input, output){
  output$plot <- renderWordcloud2({
    if (input$perspective == "positive"){
      tibble(
        term = names(sort(B[input$target_variable,], decreasing = TRUE)),
        freq = as.numeric(sort(B[input$target_variable,], decreasing = TRUE))
      ) %>%
        wordcloud2()
    }else{
      tibble(
        term = names(sort(B[input$target_variable,], decreasing = FALSE)),
        freq = -as.numeric(sort(B[input$target_variable,], decreasing = FALSE))
      ) %>%
        wordcloud2()
    }
  })
}

shinyApp(ui, server)
```
できたアプリは以下のようで

![Screen Shot 2023-01-27 at 12.07.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/8643a220-5418-a12e-f908-9b26532f495a.png)

たとえば「ポジティブかネガティブ」の項目を「negative」に変えたら

![Screen Shot 2023-01-27 at 12.07.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/6965a44b-ee38-c0a8-0dbd-2528cdffc70c.png)

可視化の内容が元の高レビュー数を示すワードクラウドから低レビュー数を示すワードクラウドになる。

次に、「変数」の項目を「Video Games」にして、「ポジティブかネガティブ」の項目を「positive」にすると、ビデオゲームのレビュー文であることを識別する単語がワードクラウドの形式で可視化される。

![Screen Shot 2023-01-27 at 12.06.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/a8453448-2358-e898-39f6-2bc29e377f9d.png)

マウス、電源、接続、純正などのゲーム・パソコン関連のワードが出ている。

最後に、「ポジティブかネガティブ」の項目を「negative」に変えたら

![Screen Shot 2023-01-27 at 12.07.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2257087/ed621131-8a6a-905c-afc3-38082a196237.png)

ビデオゲームのレビュー文ではないことを識別する単語がワードクラウドの形式で可視化される。確かにゲームの関連なさそうな内容になっている。

# 警告・注意点

以上のように作成したShinyのダッシュボードを公開する際に、特にビジネスの場面では、必然的にデータガバナンス的な問題が発生する。

たとえば、あなたの会社には、分析結果は同じ事業本部の人しか確認できないルール、もしくは社内メールアドレスでログインした人しか確認できないルールなどが存在しているかもしれない。このようなデータガバナンスに関連するルールを誤って破ってしまったら大きなインシデントに繋がりかねない。

なので、自分で最後まで実装したい気持ちが強い読者もいるかもしれないが、デプロイに関してはたとえばクラウド・データ基盤組織に、誰にどう見せていいかはデータガバナンスのガイダンスを管理する人・組織にそれぞれ相談してください。