ツイートデータを例にしてRの使い方を学ぶ

# はじめに

統計もデータ解析もRも勉強したことがありませんが、知らないままでは仕事の幅も広がらないので、少し勉強してみることにします。

今回はTwitterのツイートデータを集めるクローラを使用して、集めたツイートデータの集計をしてみたいと思います。

間違っているところや効率の悪い書き方をしている部分などあるかと思いますが、なにとぞご容赦を。

---

# 集計やデータ解析に用いられる言語

集計やデータ解析といえば、世の中では以下のような言語がよく使われていそうです。

* SQL
* Python
* R

---

# 試してみる言語

PythonとRを比較する記事はいろいろあるようでした。少し経験のあるPythonを使用するのもよいですが、食わず嫌いはよくないというのが自分の信条なので、Rを試してみることにします。

---

# 実行環境の準備

LinuxにRをインストールすると、bashなどのシェルで「R」というコマンドを実行すると、Rのコンソールが起動します。

これでターミナルでも実行できますが、[RStudio](https://www.rstudio.com/)という統合環境もあります。

RStudioはデスクトップ版とサーバ版があり、サーバ版だと、Webブラウザでサーバにアクセスして使用する形になります。

今回はサーバ版をLinuxにインストールして使用します。ついでにMeCabもインストールします。

|アプリケーションなど|バージョン|
|---|---|
|ディストリビューション|Fedora29|
|R|3.5.2|
|RStudio Server|1.1.463|
|MeCab|0.996|

---

# 基本的統計量の計算

平均値や最大値、最小値などの、代表的な統計学上の値を基本統計量と呼ぶそうです。Rで計算させてみます。データは、Rに付属しているirisのデータの一部を使用します。

```R
> petal_list <- subset(iris, Species == "setosa")$Petal.Length
> petal_list
 [1] 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 1.5 1.6 1.4 1.1 1.2 1.5 1.3 1.4 1.7
[20] 1.5 1.7 1.5 1.0 1.7 1.9 1.6 1.6 1.5 1.4 1.6 1.6 1.5 1.5 1.4 1.5 1.2 1.3 1.4
[39] 1.3 1.5 1.3 1.3 1.3 1.6 1.9 1.4 1.6 1.4 1.5 1.4

> sum(petal_list)
[1] 73.1
> mean(petal_list)
[1] 1.462
> max(petal_list)
[1] 1.9
> summary(petal_list)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   1.400   1.500   1.462   1.575   1.900 
```

---

# ツイートデータの分析

ツイートデータを表すJSON文字列を保存したテキストファイルを読み取り、いくつか集計してみます。

* 使用するデータは、2019/02/18(月)に放送されたドラマに関するハッシュタグで、放送時間中1時間に投稿されたツイートデータを集めたものとします。
* ファイルの形式は、1行にツイート1つ分のJSON文字列が記述されているとします。

---

# 各投稿者のツイート数

ドラマの放送時間中に、ユーザが何回ツイートしているかを調べてみます。

## コード

```R
library(jsonlite)
# ファイルの読み取り
tweet_json_list <- readLines("./tweet_json_list.txt")

tweet_user_list <- c()
for (tweet_json in tweet_json_list) {
  # 1行ごとにJSONを分解して、ツイートしたユーザのスクリーン名を取り出しリストに追加する
  escaped_json <- gsub("\\\\\"([^,])", "\\\"\\1", tweet_json)
  tweet <- fromJSON(escaped_json)
  tweet_user_list <- append(tweet_user_list, tweet$user$screen_name)
}
# 何件のツイートデータがあったか
length(tweet_user_list)
# 何人のユーザがツイートしたか
user_tbl <- table(tweet_user_list)
length(user_tbl)
# 1人が投稿した最大のツイート件数
max(user_tbl)
# 10回よりも多くツイートしたユーザの一覧を出力
over_10_list <- rev(sort(user_tbl[sapply(user_tbl, function(it) { it >= 10 })]))
over_10_list
# ヒストグラムを作成
histgram = hist(user_tbl)
histgram$breaks
histgram$counts
```

---

## 実行結果

ヒストグラムは以下のようになりました。1,542人がツイートしており、1,347人は5回未満のツイートですが、一方で、44回ツイートした人もいるようです。

![histgram.png](https://qiita-image-store.s3.amazonaws.com/0/27959/52f2ccd3-fab8-3f02-f6e1-41b11f39dc48.png)


```R
> length(user_tbl)
[1] 1542
> max(user_tbl)
[1] 44

> histgram$breaks
 [1]  0  5 10 15 20 25 30 35 40 45
> histgram$counts
[1] 1347  111   34   19    8    6   12    3    2
```

---

# プロフィール文に使用されている単語

放送時間中にツイートしたユーザの、プロフィールに使用されている単語の種類と件数を集計してみます。

* 集計対象の単語は名詞、動詞、形容詞、副詞のみとします。
* 1文字の名詞の単語は、絵文字や記号が多かったので、今回は集計対象から除外しています。

## コード

```R
library(jsonlite)
library(RMeCab)
target_part_of_speech_list <- c("名詞", "動詞", "形容詞", "副詞")
tweet_json_list <- readLines("./tweet_json_list.txt")

user_list <- c()
word_list <- c()
for (tweet_json in tweet_json_list) {
  escaped_json <- gsub("\\\\\"([^,])", "\\\"\\1", tweet_json)
  tweet <- fromJSON(escaped_json)
  # まだプロフィールの文章を解析していないユーザだけ処理を行う
  if (is.null(user_list) || is.na(user_list[tweet$user$screen_name])) {
    # プロフィールの文章を取り出す
    profile_txt <- c(tweet$user$description)
    if (!is.null(profile_txt) && nchar(profile_txt) > 0) {
      # 形態素解析を実行
      profile_line_txt <- gsub("\\\\n", " ", profile_txt)
      morpheme_list <- RMeCabC(profile_line_txt, dic = "./mecab-ipadic-neologd.csv.dic")
      for (morpheme in morpheme_list) {
        part_of_speech <- names(morpheme)
        word <- morpheme[[1]]
        # 対象の品詞だけリストに追加
        if (is.element(part_of_speech, target_part_of_speech_list) && nchar(word) > 1) {
          word_list <- append(word_list, word)
        }
      }
      # プロフィールの解析を行ったユーザを記録する
      names(profile_txt) <- c(tweet$user$screen_name)
      user_list <- c(user_list, profile_txt)
    }
  }
}
# プロフィールを登録しているユーザが何人いたか
length(user_list)
# 単語がいくつあるか
length(word_list)
# 単語ごとに登場する回数を集計
word_tbl <- table(word_list)
length(word_tbl)
# 30回以上登場した単語の一覧を出力
over_30_list <- rev(sort(word_tbl[sapply(word_tbl, function(it) { it >= 30 })]))
over_30_list
```

---

## 実行結果

プロフィールの文章を登録しているユーザは1,467人、単語の合計数は21,146、単語の種類の数は8,245でした。30回以上登場する単語は以下のようになりました。

```R
> length(user_list)
[1] 1467
> length(word_list)
[1] 21146

> length(word_tbl)
[1] 8245

> over_30_list
word_list
      好き       さん 関ジャニ∞     大好き       くん   フォロー 
       437        314        281        252        238        223 
   eighter     ドラマ       無言     ちゃん       こと        AAA 
       201        132        122        113        107         99 
      推し       応援   ください       映画       てる       実況 
        81         79         63         62         60         58 
      参戦     錦戸亮       音楽       する   フォロバ     アニメ 
        58         56         53         53         51         51 
      もの       あり      Nissy       line       失礼       なり 
        50         50         48         48         44         44 
      呟き ジャニーズ     エイト       大人       気軽     ファン 
        42         41         41         40         40         40 
  ツイート       いる アカウント     仲良く         20       漫画 
        40         39         39         38         36         35 
渋谷すばる         !!       自由       世代       基本       やっ 
        35         35         34         33         33         33 
    テレビ   亮ちゃん     高橋優       ない       最近     ゲーム 
        33         32         32         32         31         31 
      いい      https    http://         )/        ://   つぶやき 
        31         31         31         31         31         30 
```

---

# フォロー数とツイート数の相間

フォロー数が多い人は活発に活動していてツイート数も多いのでは、という仮説を立てて、検証してみます。検証のために、フォロー数とツイート数の散布図を作成します。ここでいうツイート数とは、放送時間1時間中のツイート数です。

## コード

```R
library(jsonlite)
library(RMeCab)
tweet_json_list <- readLines("./tweet_json_list.txt")
screen_name_list <- c()
follow_count_list <- c()
follower_count_list <- c()

for (tweet_json in tweet_json_list) {
  escaped_json <- gsub("\\\\\"([^,])", "\\\"\\1", tweet_json)
  tweet <- fromJSON(escaped_json)
  # スクリーン名、フォロー数、フォロワー数を取り出してリストで記憶
  screen_name_list <- append(screen_name_list, tweet$user$screen_name)
  follow_count_list <- append(follow_count_list, tweet$user$friends_count)
  follower_count_list <- append(follower_count_list, tweet$user$followers_count)
}
# 何件のツイートがあったか
length(screen_name_list)
# ユーザ別のツイート数を表すデータフレームを作成
tweet_data <- data.frame(table(screen_name_list))
colnames(tweet_data) <- c("screen_name", "tweet_count")
# ユーザ別のフォロー数とフォロワー数を表すデータフレームを作成
raw_data <- data.frame(screen_name = screen_name_list, follow_count = follow_count_list, follower_count = follower_count_list)
agg_data <- aggregate(x = raw_data[c("follow_count", "follower_count")], by = list(raw_data$screen_name), FUN = max)
colnames(agg_data) <- c("screen_name", "follow_count", "follower_count")
# スクリーン名でデータフレームをマージ
summary_data <- merge(agg_data, tweet_data)
# フォロー数が1000以上のユーザは図の作成から除外
subset_data <- subset(summary_data, follow_count < 1000)
# 散布図を作成
plot(subset_data$follow_count, subset_data$tweet_count)
```

フォロワー数も取り出していますが、参考までに取り出してみただけのもので、散布図の作成には使用していません。

---

## 実行結果

以下のような結果になりました。さすがに1時間という短い期間では、相間関係はなさそうです。

![follow_tweet.png](https://qiita-image-store.s3.amazonaws.com/0/27959/a028d7c7-162b-db88-0700-4580975721ed.png)


---

# おわりに

分析結果はさておき、テキストから単語を切り出したり、散布図を作成したりすることができました。

普段の開発ではRubyを使うことが多いのですが、集計処理だけならRでできるようになると、作業の効率が上がるかもしれません。精進してみます。