読者です 読者をやめる 読者になる 読者になる

kivantium活動日記

プログラムを使っていろいろやります

フォロワーのクラスタ分析

pixivのタグ頻度から考えるラブライブのカップリングが面白かったので、自分もネットワーク解析をやってみようと思いました。ネットワーク関係で最初に思い浮かんだのがTwitterのフォロワーのつながりを調べてみようと思います。

やること

  • フォロワー同士のフォロー関係を調べる
  • フォロー関係をネットワーク図で書く

コード

フォロワー同士が相互フォローにある場合二人のフォロワーの間には何らかのつながりがあることが予想されます。そこでそのつながりを見てやることでいくつかのグループが出てくるのではないかと期待できそうです。
Twitterの分析をする場合、いつもはTweepyを使っているのですが妙に遅く感じたのでPython で Twitter API にアクセスを参考にJSONをそのままリクエストします。対象はフォロワーの少ない自分のbot@mitra_sun22としました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from requests_oauthlib import OAuth1Session
import json
import time

# OAuth認証
CK = 'XXXXXXXXXXXXXXXXXXXXXX'                             # Consumer Key
CS = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'         # Consumer Secret
AT = 'XXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' # Access Token
AS = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'         # Accesss Token Secret
twitter = OAuth1Session(CK, CS, AT, AS)

# フォロワー一覧の取得
url = "https://api.twitter.com/1.1/followers/ids.json"
params = {"screen_name": "mitra_sun22"}
req = twitter.get(url, params = params)

id_to_name = {}   # IDと表示名の辞書
id_followers = [] # ID一覧
#成功していたら
if req.status_code == 200:
    ids = json.loads(req.text)
    #100個ごとに区切る
    id_lists = [ids["ids"][i:i + 100] for i in range(0, len(ids["ids"]), 100)]
    for id_list in id_lists:
        #idからscreen_nameを調べる
        url = "https://api.twitter.com/1.1/users/lookup.json"
        params = {"user_id": id_list}
        req = twitter.post(url, params = params)
        if req.status_code == 200:
            names = json.loads(req.text)
            for i in range(len(id_list)):
                # 鍵アカでなければ調査対象に
                if not names[i]["protected"]:
                    name = names[i]["screen_name"]
                    friends_count = names[i]["friends_count"]
                    id_to_name[id_list[i]] = name
                    id_followers.append(id_list[i])
        else:
            print ("Error: %d" % req.status_code)
else:
    # エラーの場合
    print ("Error: %d" % req.status_code)

# フォロー関係を調べる
url = "https://api.twitter.com/1.1/friendships/show.json"
for source in id_followers:
    for target in id_followers:
        params = {"source_id": source, "target_id": target}
        req = twitter.get(url, params = params)
        if req.status_code == 200:
            relationship = json.loads(req.text)
            if relationship["relationship"]["target"]["following"]:
                print id_to_name[source], id_to_name[target], '1'
        else:
            # エラーの場合
            print ("Error: %d" % req.status_code)
        time.sleep(6)    #API制限対策

こうすることで

mitra_sun22 kivantium 1

のようなリストが出力されます。これを適当なファイル(list.txt)に保存します。

APIが何をやっているのかはREST APIs | Twitter Developersの説明が詳しいです


次に得たリストからネットワーク図を書きます。Rを使いました。igraphというパッケージを使うので事前にinstall.packages("igraph")をR上で実行してインストールする必要があります。

library(igraph)
d <- read.table("list.txt")
g <- graph.data.frame(d[1:2],directed=T)
plot(g)

こんな感じです。

若干違うソースコードで実行した結果がこれです。
f:id:kivantium:20150421234554p:plain
大きく分けて2つのクラスタがあるように見えます。上のクラスタはリアルでの知り合いを、下のクラスタ(といっても二人ですが)はネット上で出会った人のようです。

問題点

という感じで予備実験がうまくいったので本アカでの解析を行ってみようと思ったのですが、大きな問題が二つありました。

API制限

僕のTwitterアカウントには現状で約500人のフォロワーがいるので、全ての2人の間の関係は500*500=250000通りあります。コンピューターで扱うには大したことない場合の数なのですが、Twitter APIの制限上フォロー関係は15分に180件までしか調べられないので何も考えないと全て調べるのに約15日かかることになります。組み合わせ爆発怖い。
例のお姉さんにはなりたくないので別の方法を考えます。全員のフォローリストを取得して調べれば500回APIを使うだけで済むのでずいぶん速くなりますが、それでも15分に15回しかリクエストできないことを考えると500分かかります。あまりやりたい作業ではありません。

図示の問題

情報取得に時間がかかることは置いておいて、試しに最初の方の出力結果だけを使って図を書いてみたらこうなりました。
f:id:kivantium:20150421235439p:plain
ノードの数が多すぎて何がなんだか分かりません。おそらくそれなりに関係のある人ばかりフォローしているのでフォロワー同士にもかなり多くのつながりがあることが予想されますが、それを図示したらもっとひどいことになるでしょう。

というわけで単純にネットワークを表示するのはいろいろな意味で難しそうだと分かりました。他の解析手法があったら試してみたいものですがAPI制限がきついのがいろいろと面倒です……。