( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

ついったーで自分が追加されてるListから、未フォローのアカウントをレコメンドするスクリプトを書いた。


ついったーにリスト機能が追加されましたね。いろいろ賛否両論ありますし、クライアントがまだ対応してないんですが、おもしろい機能だと思います。


というのは、リスト機能は人力によるユーザのタグ付けという側面を持っているからです。
その人がどんな人なのかは、追加されているリストを見ればなんとなくわかるという。


リストのデータから特徴抽出してクラスタリングすれば、より精度の高いユーザのクラスタ分析が可能になるでしょう。
(そのためにはクローリングしてリストのデータを収集する必要がありますが・・・。)


前置きはともかく、表題のとおり自分が追加されてるListから、未フォローのアカウントをレコメンドするスクリプトを書きました。

コード

レコメンドのアルゴリズムはすごく単純で、追加されているリストのフォロワー数を元に標準偏差を算出して、(フォロワー数/標準偏差)をウェイトとしてポイントを算出しています。


多くのリストに追加されているアカウントは、自分と近いクラスタにいるはずだし、フォロワー数が多いリストは信頼の置けるリストである、という根拠に基づいています。


gistに貼っておきます。ダウンロー丼してコンパイルして実行すると、こんな感じで未フォローのアカウントとポイントが出力されます。

yasuc18                        :  26.0070 
ykhroki                        :  26.0070 
pk0612                         :  26.0070 
yuroyoro                       :  19.5053 
oja7                           :  16.2544 
fukunishi                      :  16.2544 
FGtatsuro                      :  16.2544 
...


実行するにはこうですよ。

$ scalac TwitterListRecommender.scala
$ scala TwitterListRecommender <your_id> <password>


で、コードです。簡単ですね。

import java.net.{Authenticator,  PasswordAuthentication}
import scala.Math._
import scala.xml._
import scala.io.Source

object  TwitterListRecommender{

  val url ="http://twitter.com/%s/lists/memberships.xml"

  // Usage : scala TwitterListRecommender <your_id> <your_password>
  def main(args:Array[String]) = {
    val Array( userid, passwd ) = args

    // Basic認証
    Authenticator.setDefault(
      new Authenticator {
        override def getPasswordAuthentication =
          new PasswordAuthentication( userid,  passwd.toCharArray)
      }
    )

    // 登録されているListを取得する
    val source = Source.fromURL( url.format( userid) )
    val xml = XML.loadString( source.getLines.mkString )
    val lists = xml \\ "list"

    // Listの購読者数から標準偏差を算出
    val sd = {
      val cnts = lists.map{ l => (l \ "subscriber_count" text ).toDouble + 1}
      val total = (0.0 /: cnts ){ _ + _}
      val ave = total / lists.size
      cnts.map{ i => pow( i - ave ,  2) }.foldLeft( 0.0 ){ _ + _ } / cnts.size
    }

    // followしているidを取得
    val ids = {
      val src = Source.fromURL( "http://twitter.com/friends/ids/%s.xml".format( userid ))
      val idxml = XML.loadString( src.getLines.mkString )
      (idxml \\ "id").map( _ text )
    }

    // Listから集計
    (  Map.empty[String, Double] /: lists ){ (m,l) =>
      // (購読者数 + 1)/標準偏差 * 100 がweight
      val weight = (( l \ "subscriber_count" text ).toDouble + 1) / sd * 100
      // Listのmemberを取得する
      val src = Source.fromURL( "http://twitter.com%s/members.xml".format( l \ "uri" text ))
      val lxml = XML.loadString( src.getLines.mkString )

      // すでにfollowしているIDはのぞく
      ( lxml \\ "user").filter{
        user => ids.exists( _ == (user \ "id" text) ) == false
      }.foldLeft( m ){
        (mm , user) => {
           val name = user \ "screen_name" text ;
           mm + ( name -> ( mm.getOrElse( name ,0.0 ) + weight ))
        }
      }
    }.toList.sort{ case( (_, v1), (_, v2) )=> v1 > v2 }.foreach{
      case ( name, v ) => println( "%-30s :  %.4f ".format( name, v))
    }
  }
}

おまけ

最近こんなものをちょろちょろ書いてますが、イラストの著作権が不明なので公開は見送ってます。

Share photos on twitter with Twitpic