TwitterAPIとRで広告ターゲティング検討

## 概要

Twitter広告には「興味関心およびフォロワーが似ているアカウントのターゲティング」という広告商品があります。

参考：[興味関心およびフォロワーが似ているアカウントのターゲティング](https://business.twitter.com/ja/help/campaign-setup/campaign-targeting/interest-and-follower-targeting.html)

「任意のアカウントのフォロワーと似ているアカウント」に対してターゲティングできることになるのですが、取り扱うドメインの事前知識なしにアカウントを検討するのは難しい場合もあります。

そこでこの記事ではRを用いてツイートのデータを取得・整理し、Twitter広告でターゲティングするアカウントを探索的に検討してみます。

## 事前の設定

### パッケージの読み込み

今回の分析に必要となるRのパッケージを読み込みます。

```R
library(tidyverse)  # tidyなデータハンドリング
library(rtweet) # TwitterAPIラッパー
library(igraph) # ネットワーク分析
```
### API周りの認証設定

rtweetパッケージでTwitterAPIをコールするので事前に認証周りの設定をしておきます。
必要なパラメータは[Twitter Developer](https://developer.twitter.com/ja/docs)から事前に取得しておきましょう。

```R
appname <- "app_name"
consumer_key <- "consumer_key"
consumer_secret <- "consumer_secret"
access_token <- "access_token"
access_secret <- "access_secret"

twitter_token <- create_token(
  app = appname,
  consumer_key = consumer_key,
  consumer_secret = consumer_secret,
  access_token = access_token,
  access_secret = access_secret)
```

## ツイートの取得と可視化

今回は「育児」というカテゴリでの架空の出稿を想定して、ツイートデータを取得します。

### 1) 任意の文字を含める人気のツイートから、アカウントを探す

任意のキーワードを含むツイートを検索し、その中でリツイートの多いツイート順に並び替えてみます。

```R
result <- search_tweets(q = "育児", n = 10000)

result_desc <- result %>% 
  filter(is_retweet == "FALSE") %>% 
  arrange(desc(retweet_count)) %>% 
  slice(1:20) %>% 
  select(screen_name,text,retweet_count) %>% 
  mutate(text = str_replace_all(text, regex(pattern = "http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&=]*)?"),"")) %>%
  mutate(text = str_replace_all(text, regex(pattern ="\n",),"")) %>% 
  mutate(text = str_to_lower(text))

result_desc
```

以下のようなツイートが取得できました。リツイート数上位のアカウントは、もしかしたら検索キーワードと関連性の高いアカウントかもしれません。

```R
## # A tibble: 5 x 3
##   status_id         text                                           retweet_count
##   <chr>             <chr>                                                  <int>
## 1 1359125065674223… 3人の育児をしながら世界で活躍するミランダ・カーが、毎日続け…             7
## 2 1359129308724224… 「家事育児完璧にしろよ」みたいなこと言う男はよほど日頃完璧に…             4
## 3 1359136353582080… 世界一家事育児を分担しない自国のオスシングル家庭の養育費未払…             2
## 4 1359131377040453… 八幡と結婚して子供産まれた後のゆきのんは家事と育児の邪魔だか…             2
## 5 1359129094898675… 産んだ後に引き取って育児の責任を全部引き受ける男性が増えれば…             2
```

### 2) 任意の文字を含めるツイートから、フォロワー数の多いアカウントを探す

任意のキーワードを含むツイートを検索し、その中でフォロワー数の多いアカウント順に並び替えてみます。

```R
result <- search_tweets(q = "育児", n = 10000)

result_desc <- result %>% 
  distinct(text,.keep_all = TRUE) %>% 
  users_data() %>% 
  distinct(user_id,.keep_all = TRUE) %>% 
  arrange(desc(followers_count))%>% 
  slice(1:200) %>% 
  select(screen_name,description,followers_count) %>% 
  mutate(description = substr(description, 0, 30))

result_desc
```
以下のようなツイートが取得できました。フォロワー数で上位のアカウントはもしかしたら検索キーワードと関連のあるアカウントかもしれません。

```R
## # A tibble: 2,421 x 3
##    screen_name    description                                    followers_count
##    <chr>          <chr>                                                    <int>
##  1 hTcBbWbLse4Tt… "🌟Twitterのみなさん、こんにちは➰🍀みんなの人気者🌟ナ"…           64667
##  2 rottaik        "ろった。漫画家。育児しつつ新連載準備中。『リメイク』『メイク"…           58231
##  3 iitripod       "ツイッター歴10年。RT多め。ネトウヨ風リベラル左翼。アンチ"…           54309
##  4 docomo_cs      "NTTドコモ公式のお客様サポートアカウントです。\nドコモの商"…           35907
##  5 pp_jiro0       "良夫賢父のビッグダディ"                                 34890
##  6 terrakei07     "インターネットで文章を書いたり、本を出したり、ラジオで喋った"…           32184
##  7 nandemonai_ni  "肩書き不要、なんでもない人間です。2015年2月〜2016年"…           32018
##  8 nuno40801      "北海道の小児科専門医. なるべく根拠に基づいて小児科関連の情"…           20532
##  9 comicessay     "WEBマンガ誌「コミックエッセイ劇場」の公式アカウントです。"…           19264
## 10 tobalog        "ミニマリストの対岸にいる人。『トバログ』でブログとYouTu"…           18887
## # … with 2,411 more rows
```

### 3) ネットワークを可視化して上位のアカウントを探す

任意のキーワードでツイートしている人たちのネットワーク図を可視化し、コミュニティの中心にいるアカウントを把握してみます。

ネットワークグラフを用いることで、リツイートやいいねなどでネットワーク上の上位に存在するアカウントを可視化することができます。
ここではigraphパッケージを利用して「特定のキーワードにおけるリツイート数が多い」アカウントのネットワーク分析を実施します。

```R
result <- search_tweets(q = "育児", n = 10000)

result_retweet_desc <- result %>% 
  arrange(desc(retweet_count)) %>% 
  slice(1:100)

# igraphで描画するためにネットワークデータのデータフレームに変換
result_network <- network_graph(result_retweet_desc)

# igraphでプロット
plot(result_network,
     vertex.size=8, 
     vertex.label=V(result_network)$name, 
     vertex.label.font=2,
     vertex.frame.color="white", 
     vertex.label.cex=0.8, 
     edge.width=E(result_network)$weight,
     layout=layout.fruchterman.reingold
)
```

いくつかのアカウントをフォローする形で複数のグループで分かれていることが確認できました。ここではリツイート元のアカウントが中心に位置しますが、そのアカウントに注目することで何か気づきが得られるかもしれません。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3134373638392f39313832316230382d343139302d303233632d636261382d6539363930353761383462632e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147689/9b548a8e-3f31-aa79-0df0-03b2ea182d4e.png)


### 4) 任意のユーザーのフォロワーを、フォロワーの多い数順で並び替える

任意のアカウントのフォロワーはそのアカウントと関連性が高いかもしれません。そこで任意のアカウントのフォロワーを、フォロワー数の多い順で並び替えてみます。

ここではau公式アカウント([@au_official](https://twitter.com/au_official))を例にして抽出してみます。

```R
coreUserName = "au_support"
user_total <- get_followers(coreUserName, n = 75000)

twitterUser_follower_df <- get_followers(
  coreUserName, n = nrow(user_total), retryonratelimit = TRUE
)
twitterUser_follower_df <- lookup_users(twitterUser_follower_df$user_id) %>%
  users_data()

## フォロワー数の降順で並び替え
df_check <- twitterUser_follower_df %>% 
  arrange(desc(followers_count)) %>%
  select(screen_name,name,description,followers_count)
```

目視にてピックアップしたところ、以下のような関連性の高いアカウントを発見することができました。

```R
##    screen_name     name                        followers_count
## 1  UQinfo          UQ、だぞっ                  195185         
## 2  WebMoney_INFO   auペイメント（WebMoney）     97400         
## 3  smartpass_au    auスマートパス               91052         
## 4  BIGLOBE         BIGLOBE                      89661         
## 5  au_PAY_market   au PAY マーケット            23124         
## 6  aubookpass      ブックパス@au以外も使える！  16848         
## 7  utapass         うたパス                     16349         
## 8  up_now_official uP!!!【公式】                 9718         
## 9  auAM_official   auアセットマネジメント        7283         
## 10 au_ena          auサポーとり エナ 【公式】    5573         
## 11 adp_au          au Design project             4560
```


## その他、ちょっとした工夫

### 前処理

APIから取得したツイート情報のデータフレームはそのままでは扱いづらいため、必要に応じて以下のような前処理を実施します。

#### URL文字列除去

多くのツイートにはURLが含まれていますので、形態素解析する場合には予めURL文字列を除去しておきます。

```R
mutate(text = str_replace_all(text, regex(pattern = "http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&=]*)?"),""))
```

#### bot除去

人ではなくbotによる自動投稿も少なくないため、Twitterクライアントを判定してbotと思われるレコードを除去します。

```R
filter(source != "twittbot.net")
```

#### リツイートの重複排除

リツートが行として重複すると扱いづらいためリツイートのレコードを除去します。

```R
filter(is_retweet == "FALSE")
```

#### ノイズとなるアカウントの除去
海外のスパムアカウント等、フォロワー数が多くてもノイズに近いようなアカウントが抽出されることもあります。このため必要に応じて、認証済みアカウントやプロフィールでの公式の文字列の有無をフィルターの条件とします。

```R
filter(verified == TRUE) ## 認証済みアカウント
filter(str_detect(description, "[亜-熙ぁ-んァ-ヶ]")) ## プロフィールでの日本語存在チェック
filter(str_detect(description, "[公式]")) ## 公式の文字列チェック
```

### APIの制約

TwitterAPIにはリクエスト制限があり、APIのレスポンスで `Rate limit exceeded` エラーが返ってきたら、それは制限を超えていて利用できない状態になっています。

Rのパッケージである`rtweet`では`rate_limit()`を実行することでAPIの使用状況を確認できます。以下のような形で残リクエスト数が0%ではないエンドポイント一覧を出力可能で、利用可能になるまでの残時間も出せます。

#### エンドポイントごとの利用状況

```R
rate_limit() 
```

```R
## # A tibble: 220 x 7
##    query    limit remaining reset  reset_at            timestamp           app  
##    <chr>    <int>     <int> <drtn> <dttm>              <dttm>              <chr>
##  1 lists/l…    15        15 15.01… 2021-02-10 22:06:11 2021-02-10 21:51:11 test…
##  2 lists/m…    75        75 15.01… 2021-02-10 22:06:11 2021-02-10 21:51:11 test…
##  3 lists/s…    15        15 15.01… 2021-02-10 22:06:11 2021-02-10 21:51:11 test…
##  4 lists/m…   900       900 15.01… 2021-02-10 22:06:11 2021-02-10 21:51:11 test…
##  5 lists/s…    15        15 15.01… 2021-02-10 22:06:11 2021-02-10 21:51:11 test…
##  6 lists/s…    75        75 15.01… 2021-02-10 22:06:11 2021-02-10 21:51:11 test…
##  7 lists/o…    15        15 15.01… 2021-02-10 22:06:11 2021-02-10 21:51:11 test…
##  8 lists/s…   180       180 15.01… 2021-02-10 22:06:11 2021-02-10 21:51:11 test…
##  9 lists/m…    15        15 15.01… 2021-02-10 22:06:11 2021-02-10 21:51:11 test…
## 10 lists/s…   900       900 15.01… 2021-02-10 22:06:11 2021-02-10 21:51:11 test…
## # … with 210 more rows
```
例えば利用中のエンドポイントで絞る場合は以下のようになります。

```R
rate_limit() %>% 
  filter(limit != remaining)
```

```R
## # A tibble: 2 x 7
##   query     limit remaining reset  reset_at            timestamp           app  
##   <chr>     <int>     <int> <drtn> <dttm>              <dttm>              <chr>
## 1 applicat…   180       176 13.46… 2021-02-10 22:05:32 2021-02-10 21:52:05 test…
## 2 search/t…   180       170 14.06… 2021-02-10 22:06:08 2021-02-10 21:52:05 test…
```

## おわりに
実際にターゲティング用途でAPIを利用してみたところ、想定外のアカウントの発見に繋がりました。Twitter広告側でもある程度のユーザー拡張はできるはずですが、最初のアカウント探しという意味ではAPIが活用できそうな印象です。

## 参考文献
- [Rとigraphを使ったネットワーク解析と可視化](http://www.nemotos.net/igraph-tutorial/NetSciX_2016_Workshop_ja.html)
- [Package ‘rtweet’](https://cran.r-project.org/web/packages/rtweet/rtweet.pdf)
- [Follow, search, and get users](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list)
- [ropensci/rtweet](https://github.com/ropensci/rtweet)