Archives

建築批評空間を可視化してみた

最近Pythonにはまっています。

ついでに機械学習の勉強にもはまっています。

こりゃあ建築批評空間を可視化するしかあるまい。

公開されている建築批評といえば「『10+1』データベース」ですね。

なんとなく見た感じ、アドレスは

スクリーンショット_13_02_23_19_26

こんな感じになってるので、適当に番号を増やしてったところ、どうやら1549まであるみたい。

というわけで、全ページを次のシェルスクリプトで取得します

#
var1=http://db.10plus1.jp/backnumber/article/articleid/
var2=.html

for ((i=1;i<1550;i++))
do wget -O $i$var2 $var1$i/
done;

この1549の中にも歯抜けになってる番号が100ありました。

それで、記事によっては

スクリーンショット_13_02_23_19_06

こんな具合に公開されてないものもあります。全1449記事のうち、公開数:721、未公開数:728 でした。約半数が公開されているようです。

さて、いろいろやってみたいことはあるのですが、まずは簡単に記事同士の関係をみてみようと考えました。

記事を見てみると、各記事の右側には著者名が並び、その記事の掲載号情報があり、以降は文中で言及されるキーワードが解説されていることが分かります。

スクリーンショット_13_02_23_19_10-2

 

この著者としての登場回数とか、誰の記事で誰がよく言及されるとかを調べると面白そうです。というわけで、htmlソースから該当部分をPythonでBeautifulSoupを使って抜き出し、pymongoを使ってmongodbにブッ込みました。

なんでmongodbかというと、あとでheroku上でなんか作ろうかなとか思ってるからです。今回はやりませんが。

スクリーンショット_13_02_23_19_22

 

当該情報はannotationクラスのdiv内にあるので、

# -*- coding: utf-8 -*-
import sys
import codecs
import re
import pymongo
from BeautifulSoup import BeautifulSoup

sys.stdin  = codecs.getreader('utf-8')(sys.stdin)
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)

R_END_AUTHOR = re.compile(r'『10+1』')

def getNavigableStrings(soup):
  if isinstance(soup, BeautifulSoup.NavigableString):
    if type(soup) not in (BeautifulSoup.Comment,
      BeautifulSoup.Declaration) and soup.strip():
      yield soup
  elif soup.name not in ('script', 'style'):
    for c in soup.contents:
      for g in getNavigableStrings(c):
        yield g

def parseAuthorAndKeyword(num, col):
    filename = 'html/'+str(num)+'.html'
    f = open(filename, 'r')
    data1 = f.read()
    f.close()
    soup = BeautifulSoup(data1)
    enc = soup.originalEncoding
    # print "Encoding : %s" % enc
    # print unicode(soup.prettify(), enc)

    author  = []
    keyword = []
    authorFlag = True
    # author, keyword
    for table in soup('div', {'class':'annotation'}):
        # get content name
        name = table.findAll('h5')[0].next.next.string.encode('utf-8')
        if authorFlag:
            if R_END_AUTHOR.match(name):
                authorFlag = False
                continue
            splitted = name.split('(')
            author.append(splitted[0])
        else:
            splitted = name.split('(')
            keyword.append(splitted[0])

    # article
    # locked
    articleFlag = -1
    # limited
    for article in soup('div', {'class':'articleLimitedUnitBox'}):
        articleFlag = 0
    # published
    for article in soup('div', {'class':'articleUnitBox'}):
        articleFlag = 1

    doc = { 'author': author, 'keyword': keyword, \
            'number': num, 'published': articleFlag }
    col.insert(doc)

def main():
    conn = pymongo.Connection()
    db = conn['10+1']
    col = db['authors']

    for i in range(1, 1550):
        print i
        parseAuthorAndKeyword(i, col)

    conn.disconnect()

if __name__ == '__main__':
    main()

こんな具合で抜き出します。

関数parseAuthorAndKeywordで抜き出しを行なっています。BeautifulSoupで当該タグまで下り、table.findAll(‘h5’)[0].next.next.string.encode(‘utf-8’)で抜き出しています。名前にリンクが付いている人と付いてない人とがいたので、どっちでもいけるようちょっと汚くなっています。

それと、だいたい名前は「磯崎新(イソザキ・アラタ)」みたいな形になってて、カッコの中はいらないので、splitted = name.split(‘(’)でカッコの始まりで分割し、splitted[0]で前半部分だけ保存してます。

pymongoについてはググれば分かります。

次は、著者として登場するすべての人物について、その人の記事中であるキーワードが右に解説されていたら著者→キーワードとなる辺を持つようなグラフ(隣接辺リスト)をつくります。辺の重みはその記事数です。

さて、いよいよ可視化です。可視化にはcytoscapeを使おうと思ってやってみたんですが、web上で見るのに不便だなと思ったので、もうちょっとイケてるGephiを使って、web上での閲覧はgexf-jsという素晴らしいライブラリを使わせてもらうことにしました。

cytoscapeで読み込む隣接辺リストはsource, target, weightを一行とするcsvファイルでいいのですが(この辺は井庭崇先生のブログがわかりやすい)、Gephiの場合はsource, targetを一行とするcsvファイルです。

重みについてはすべての辺の重みを1として、同じsource, targetの辺の数がその辺の重みとなります。

# -*- coding: utf-8 -*-
import sys
import codecs
import pymongo

sys.stdin  = codecs.getreader('utf-8')(sys.stdin)
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)

convert_tuples = [
 (u'\u00a6',u'\u007c'),#broken bar=>vertical bar
 (u'\u2014',u'\u2015'),#horizontal bar=>em dash
 (u'\u2225',u'\u2016'),#parallel to=>double vertical line
 (u'\uff0d',u'\u2212'),#minus sign=>fullwidth hyphen minus
 (u'\uff5e',u'\u301c'),#fullwidth tilde=>wave dash
 (u'\uffe0',u'\u00a2'),#fullwidth cent sign=>cent sign
 (u'\uffe1',u'\u00a3'),#fullwidth pound sign=>pound sign
 (u'\uffe2',u'\u00ac'),#fullwidth not sign=>not sign
]

def unsafe2safe(string):
    for unsafe, safe in convert_tuples:
        string = string.replace(unsafe, safe)
    return string

def frequencyOfItemsInKey(col, key):
    """
    return dic of all items and its frequency appeared in key.
    {key1:val1, key2:val2, ...}
    """
    dic = {}
    # for all documents
    for doc in col.find():
        # for each key
        for i in  range(len(doc[key])):
            buff = {doc[key][i]: \
                    col.find({key: doc[key][i]}).count()};
            dic.update(buff)

    return dic

def frequencyOfSecondInFirst(col, firstKey, first, secondKey):
    """
    return frequency of keyword in author's text.
    {first: {second1:val1, second2:val1, ...}}
    """
    buff = {}
    # for all documents of which firstKey is first
    for doc in col.find({firstKey:first}):
        # for each secondKey
        for i in  range(len(doc[secondKey])):
            buff2 = {doc[secondKey][i]: \
                     col.find({firstKey: first, \
                               secondKey: doc[secondKey][i]}).count()
                    }
            buff.update(buff2)
    dic = {first: buff}

    return dic

def writeDicRankToTSV(dic, filename):
    """write dic to TSV as a ranking of the value."""
    f = codecs.open(filename, 'w', 'utf-8')
    for key, val in sorted(dic.items(), key=lambda x:x[1], reverse=True):
        if key == '\n': continue
        buff = str(key)+'\t'+str(val)+'\n'
        f.write(buff)
    f.close()

def makeASetOfKey(col, key):
    """make a key set."""
    keys = []
    for doc in col.find():
        for i in  range(len(doc[key])):
            keys.append(doc[key][i])
    keys = set(keys)
    return keys

def writeNetworkAsEdgeList(network, filename):
    """for cytoscape."""
    f = codecs.open(filename, 'w', 'shift-jis')
    for person in network.keys():
        for keyword in network[person].keys():
            buff = person+','+ \
                   keyword+','+str(network[person][keyword])+'\n'
            buff = unsafe2safe(buff)
            f.write(buff)
    f.close()

def writeNetworkAsEdgeListGephi(network, filename):
    """for gephi."""
    f = codecs.open(filename, 'w', 'utf-8')
    for person in network.keys():
        for keyword in network[person].keys():
            # repeat for edge weight
            for i in range(network[person][keyword]):
                buff = person+','+keyword+'\n'
                f.write(buff)
    f.close()

def main():
    # connect with mongodb
    conn = pymongo.Connection()
    db = conn['10+1']
    col = db['authors']

    # make ranking of authors and keywords
    dic = frequencyOfItemsInKey(col, 'author')
    writeDicRankToTSV(dic, 'authorRank.txt')
    dic = frequencyOfItemsInKey(col, 'keyword')
    writeDicRankToTSV(dic, 'keywordRank.txt')

    # make a reference network
    authors = makeASetOfKey(col, 'author')
    dic = {}
    # for all authors
    for person in authors:
        buff = frequencyOfSecondInFirst(col, 'author', person, 'keyword')
        dic.update(buff)
    writeNetworkAsEdgeList(dic, 'referenceNetwork.csv')
    writeNetworkAsEdgeListGephi(dic, 'referenceNetworkForGephi.csv')

    # basic information
    numPages = col.count()
    numLocked = col.find({'published': -1}).count()
    print u'全記事数', numPages - numLocked
    print u'未公開記事数', col.find({'published': 0}).count()
    print u'公開済み記事数', col.find({'published': 1}).count()

    # disconnect with mongodb
    conn.disconnect()

if __name__ == '__main__':
    main()

ちなみにcytoscapeはshift-jisでないといけないので、この記事のやり方でutf-8から変換しています。Gephiはutf-8でいける。

さて、まずはそのまんま可視化した場合(画像クリックで開きます)。

スクリーンショット_13_02_23_18_54

 

  • 言及されてる回数が多いほどノードが大きく、
  • 言及数が多い人(多くのキーワードに言及している人)ほど赤く、
  • 文字の大きさは両方を合わせた数が多いほど大きくなっています

あとで言いますが言及数は記事数が多いほど多いですね当然。

では次は次数が1(言及、被言及合わせて1回だけ)のノードを排除し、その上で辺の重みが2以下の辺を排除したグラフです。いわばテンプラスワン批評空間における特権階級たちです。

スクリーンショット_13_02_23_18_53

 

では記事投稿数ランキング上位21人(同率がいるため)です。

五十嵐太郎 72
田中純 55
日埜直彦 47
八束はじめ 38
塚本由晴 34
上野俊哉 32
中谷礼仁 29
篠儀直子 29
内田隆三 29
今村創平 27
吉村靖孝 26
南泰裕 23
石川初 19
磯崎新 19
椹木野衣 18
多木浩二 17
大島哲蔵 16
今井公太郎 16
毛利嘉孝 15
西沢大良 14
貝島桃代 14

椹木野衣さん多いんですね。意外でした。それでは被言及ランキング上位21人です。

ル・コルビュジエ 114
ポストモダン 92
磯崎新 80
レム・コールハース 64
伊東豊雄 54
丹下健三 50
INAX出版 48
ヴァルター・ベンヤミン 47
ルネサンス 47
パサージュ 35
安藤忠雄 35
藤森照信 35
バウハウス 34
原広司 33
メタボリズム 32
現代住宅研究 31
アルゴリズム 30
アーキグラム 29
ミース・ファン・デル・ローエ 28
コーリン・ロウ 28
篠原一男 28

コルビュジェ先生はさすがですね。テンプラスワンがポストモダンの建築批評誌であることがよく分かります。INAX出版はやりよるなという感じですかね。

これからはもうちょっと面白い特徴を見つけてみようと思います。

あと本文抽出もできるのでMeCabを使ってなんかしてみようと思います。まずは多分ナイーブベイズを試すと思われます。