昨日の方法でも、その検索対象が頻出語でなければいいのだが、すぐ近くに続けて姿を見せるときには判りにくい結果表示になってしまう。重なって出てくる場合は逃してしまう。普通の文章なら重なることはあまりないと思うが、例えば、「そんなわけ、ないないない!」という文を「ないない」で検索したとき、「『ないない』ない」と「ない『ないない』」の二つを拾えるかどうかということである。塩基配列の検索に応用したいときにも、これができるかどうかは大きな意味を持つ(かも知れない)。
 そこで昨日の、文書を検索語で分割して処理するところを以下のように変更してみる。文書の頭からの位置もこれなら表示できる。検索文字列が検索対象範囲内に見つからない場合は-1が返ってくるので、以下のように書いてみた。
strt = 1 while strt > 0: pos = txt.find(word,strt) if pos == -1: break left = txt[pos-20:pos] right = txt[pos:(pos + len(word) + 20)] print left + right + " : " + '%s' %(pos) strt = pos + 1というようにして、python kwic3.py string test.txtと打つと、
re are two types of strings in Python: byte st : 1121 ngs in Python: byte strings and Unicode string : 1145 strings and Unicode strings. As you may have g : 1165 ave guessed, a byte string is a sequence of by : 1206 This creates a byte string: byteString = "hell : 1454 s creates a Unicode string: unicodeString = u" : 1540 ld!" Convert a byte string into a Unicode stri : 1603 ring into a Unicode string and back again: s = : 1625 in: s = "hello byte string" u = unicode( s ) b : 1664 the encoding of the string: s = "hello normal : 2042あと、昨日の方法では表示されない結果も、ちゃんと拾い上げる。Encodeで検索すると、
n exception: UnicodeEncodeError: 'ascii' codec : 3686という結果が表示されて然るべきなのだが、昨日の方法だと出てこない。どうしてなのだろう。
次は、前後に出てくる単語によって並べ替えたりしたい。
昨日は一行一文に整形された文書を各行一つの検索語のみ表示させてみたが、今日は文書ファイルを全部読み込んで、検索語を次々に拾って、前後を切り取って表示させていくようにしてみた。
#!/usr/bin/python
# vim: set fileencoding=utf-8 :
import sys
import re
word = sys.argv[1]
file = sys.argv[2]
input = open(file,'r').read()
txt = re.sub('\x0D\x0A|\x0A|\x0D', '\n', input)
txt = re.sub('(\n)+', ' ', txt)
txt = re.sub('\t', ' ', txt)
txt = re.sub('<.+?>', ' ', txt)
blocks = txt.split(word)
i = 0
for block in blocks:
  if i > 1:
    left = prev[-20:]
    right = block[0:20]
    print left.rjust(20) + word + right
  prev = block
  i = i + 1
 html文書のタグを取り除くようにしている。改行コードはこれではうまく変換されていないが、どうしたらいいのか、よく判らない。PHPのときにはこれでできていた。
$ python kwic2.py string test.txt
  s in Python: byte strings and Unicode 
      s and Unicode strings. As you may have g
ave guessed, a byte string is a sequence of by
This creates a byte string: byteString = "hell
s creates a Unicode string: unicodeString = u"
ld!" Convert a byte string into a Unicode 
     into a Unicode string and back again: s =
 こんな具合になる。Pythonってi++は使えないのかな。何もかもまだよく判らない。まだ語頭が大文字だと逃してしまったりする。
 今日はkwicの練習である。PHPのときも、Rubyのときも、これをやった。そもそも、PHPを最初に使いたいと思った理由の最も大きな理由の一つは、このKwicである。Key word in contextの略で、キーワードがどんな文脈で使われているかを調べるために、検索したい語の前後を含めて抽出するという作業である(多分)。だから、kwicは私に乗っての"Hello World!"のようなものだ。
 いつものように、ロード・ダンセイニの『ペガーナの神々』から、言葉を見つけてみる。今回は、一行一文に整形済みのものを対象としている。
#!/usr/bin/python
import sys
import re
word = sys.argv[1]
file = sys.argv[2]
input = open(file,'r')
lines = input.readlines()
for line in lines:
  if word in line:
    line = re.sub('\n','',line)
    block = line.split('\t')
    parts = block[3].split(word)
    left = parts[0][-20:]
    right = parts[1][0:20]
    print left.rjust(20) + word + right
 とりあえずは、こんな感じで。
$python kwic1.py knoweth pegana.txt -YOOD-SUSHAI--_none knoweth._." d of song, but none knoweth, for who hath heard hings--whither Mung knoweth." Lahai be true, none knoweth saving only MANA-YO they find that MANA knoweth that they have made NA-YOOD-SUSHAI, and knoweth the wherefore of th know only that man knoweth not." ok: 'Alhireth-Hotep knoweth All Things, for he said Imbaun: 'What knoweth the Prophet of theいちおう、それらしい結果が表示されている。検索語の左側が20文字に満たない場合でも、left = parts[0][-20:]でエラーは出ないようだ。が、これでは一行に複数回検索語が含まれていたとしても、最初の一つしか表示されない。行番号も表示させたりしたい。そのあたりのことは、また今度。その後は、前後の単語のアルファベット順とか出現頻度順で並べ替えるということもしなければならない。
Pythonで英語の単数形と複数形の変換ができるという記事を読んだ気がしていたのだが、Rubyだった(「Rubyを仕事に使うべし!」 IT-Pro)。なんだ、Rubyだったかと思ったが、一応試してみた。
RubyGemsとかRailsとか入れなければならないようだ。Synapticでrubygemsをインストールしたのに、gemなんてインストールされていないという警告しか返ってこない。変だなと思いながら、http://rubyforge.org/から最新版をダウンロードして、
sudo ruby setup.rbだけでインストールはされたのだが、
gem install railsと打っても、gemなんてないねと云われてしまう。
sudo gem1.8 install rails --include-dependenciesとやったら動いた。よく見ると、rubygemsのインストール時に、
install -c -m 0755 /tmp/gem /usr/bin/gem1.8と表示されていた。ubuntuではそうなのか。そういえば、前にgem1.8からgemへシンボリックリンクを張ったような気がする。まだ、Rubyを諦めていなかったころに。こうして、使う予定もないのにrailsをインストールしてしまって、複数形から単数形への変換をさせてみた。単複同形の語、違う形になるもの、-iumから-aになるもの、-ex/ixから-icesになるもの、-enがつくものなど。普通のsがつくものも入れてみる。
#!/usr/bin/ruby require 'rubygems' require 'active_support' swords = ["sheep", "person", "medium", "bacterium", "index", "matrix", "ox", "egg"] pwords = ["sheep", "people", "madia", "bacteria", "indices", "matrices", "oxen", "eggs"] puts "Singular -> Plural" for sw in swords puts "\t" + sw + " => " + sw.pluralize end print "\n" puts "Plural -> Singular" for pw in pwords puts "\t" + pw + " => " + pw.singularize endこれを実行させてみると、
Singular -> Plural
        sheep => sheep
        person => people
        medium => media
        bacterium => bacteria
        index => indices
        matrix => matrices
        ox => oxen
        egg => eggs
Plural -> Singular
        sheep => sheep
        people => person
        madia => madium
        bacteria => bacterium
        indices => index
        matrices => matrix
        oxen => ox
        eggs => egg
ちゃんとできているではないか! 上記の記事では、「これが何の役に立つの? と言われると,ちょっと微妙な気もしますが。」なんて書いてあったが、単語リストを作ったり、索引を作ったり、検索するときに役立ちそうではないか。Pythonではできないのか。
この前作った単語リストの頻度順を高い方からにするのは、
freq = sorted(wDict.items(), key=lambda (k,v): (v,k), reverse=True)とすればいいことが判った。, reverse=Trueを付けるということである。なるほどね。
昨日、中身ではなくファイル名となってしまうと書いていたところは、
tree = ET.fromstring(udoc)と書けばいいだけだった。しかし、このTechnobahnのRSSでは、エラーが出てしまう。形が許されないとかなんとか。この書き方で他のRSSは読み込めたので、Technobahnの書き方が悪いのだろう。仕方がないので、テキストを分解していって取得してみる。リンク先の本文も。第一段落目をSummaryとして別に切りだしている。
#!/usr/bin/python
# vim: set fileencoding=utf-8 :
import re
from urllib import urlopen
import xml.etree.ElementTree as ET
def honmon(link):
  doc = urlopen(link).read()
  udoc = unicode(doc,"euc-jp").encode("utf-8")
  strt = udoc.index('【Technobahn')
  stop = udoc.index('<font size=2>',strt)
  ftext = udoc[strt:stop]
  ftext = re.sub('【.+?】','',ftext)
  ftext = re.sub('<.+?>','',ftext)
  brbr = udoc.index('<br><br>',strt)
  summary = udoc[strt:brbr]
  summary = re.sub('【.+?】','',summary)
  summary = re.sub('<.+?>','',summary)
  return [summary,ftext]
doc = urlopen('http://www.technobahn.com/rss/science.xml').read()
udoc = unicode(doc,"euc-jp").encode("utf-8")
strt = udoc.index('</channel>')
stop = udoc.index('</rdf:RDF>')
body = udoc[strt+len('</channel>'):stop]
body = re.sub('^\n','',body)
items = body.split('</item>\n')
items.pop()
for item in items:
  values = item.split('\n')
  title = re.sub('<.+?>','',values[1])
  link = re.sub('<.+?>','',values[2])
  ptime = re.sub('<.+?>','',values[5])
  print title
  print link
  print ptime
  texts = honmon(link)
  print texts[0]
こんなかんじで、
1セント硬貨が10億円に変身、18世紀の貴重な硬貨301枚がオークションに http://www.technobahn.com/news/2008/200802190539.html 2008-02-19T05:39:00+0900 「1セントに笑うものは1セントに泣く」ということわざもあるが、今から 約200年前の米1セント硬貨、合計301枚が15日、米国内でオークションに 掛けられて落札金額の合計は1007万ドル(約11億円)にも及んだ。ずらずらと書き出されてくる(記事全文は表示させていない)。この時刻を、エポックタイムからの秒数に変換したいのだが、どうしたらいいのか判らない(PHPではタイムスタンプと云っていたように思う)。困ったな。
今日はXMLを扱えるようになりたい。と思ったのは実は三日前。まだ扱えるようにならないのだ。ElementTreeっていうのが便利だというので、これを試してみた。
import elementtree.ElementTree as ETというようにして使えと書いてあるので、そうしたが、「そんなものはない」という警告が出てしまう。2.5以降は標準装備なんでインストールしなくていいというのは嘘だったのか。でも、インストールしても「そんなものはない」と云われてしまう。
import xml.etree.ElementTree as ETとすれば動くというのが判るのに2日かかった。やれやれ。今はこうするらしい。
新潮社の今月の新刊というRSSの情報を取得してみよう。
#!/usr/bin/python
from urllib import urlopen
import xml.etree.ElementTree as ET
rss = ET.parse(urlopen('http://www.shinchosha.co.jp/feed/syoseki.xml')).getroot()
for element in  rss.findall("channel/item"):
  print element.findtext("title")
  print element.findtext("link")
するとこんな内容が表示される。
月のころはさらなり http://www.shinchosha.co.jp/book/306361/ ダンシング・ヴァニティ http://www.shinchosha.co.jp/book/314529/ 魔術はささやく〈新装版〉 http://www.shinchosha.co.jp/book/375004/ 我らが隣人の犯罪〈新装版〉 http://www.shinchosha.co.jp/book/375005/ 頭にちょっと風穴を―洗練された日本人になるために― http://www.shinchosha.co.jp/book/393702/ フェレットの冒険I―海の救助隊― http://www.shinchosha.co.jp/book/505803/ フェレットの冒険II―嵐のなかのパイロット― http://www.shinchosha.co.jp/book/505804/ 予告された殺人の記録/十二の遍歴の物語 http://www.shinchosha.co.jp/book/509013/ そうか、もう君はいないのか http://www.shinchosha.co.jp/book/310817/ 戦後日本経済史 http://www.shinchosha.co.jp/book/603596/ 日本人の愛した色 http://www.shinchosha.co.jp/book/603597/ 朝令暮改の発想―仕事の壁を突破する95の直言― http://www.shinchosha.co.jp/book/306431/ 越境者 松田優作 http://www.shinchosha.co.jp/book/306451/ 異能の画家 伊藤若冲 http://www.shinchosha.co.jp/book/602166/ 豆腐百珍 http://www.shinchosha.co.jp/book/602167/ ぬばたま http://www.shinchosha.co.jp/book/306331/ 雪になる http://www.shinchosha.co.jp/book/425604/ ワンダー・ドッグ http://www.shinchosha.co.jp/book/468002/ エイリアンの地球ライフ―おとなの高機能自閉症/アスペルガー症候群― http://www.shinchosha.co.jp/book/300112/ 太田和彦の居酒屋味酒覧〈第二版〉精選173 http://www.shinchosha.co.jp/book/415805/これはこれでいいのだが、著者名をとるときにはどうすればいいのか。CDATAの中に入っているのだ。descriptionの項全体をとってくるのは簡単なのだが、そこからdiv class='writer'の部分だけ取ってくるのはどうすればいいんだ。
TechnobahnのRSSが何とEUCで書かれているの。そこで、
doc = urlopen('http://www.technobahn.com/rss/science.xml').read()
udoc = unicode(doc,"euc-jp").encode("utf-8")
tree = ET.parse(udoc)
とやってみたら、「ファイル名が長すぎる!」というエラーが出てしまう。udocが中身ではなくファイル名として認識されているようだ。どうすれば中身になるのだろう。日本語の形態素解析にはMeCabが必要。私はUTF8に設定してインストールしている。python-mecabをインストールしようと思ったら、setup.pyで何やらエラーが出てしまう。私には何のことだかさっぱり理解できない。そこで、The Ubuntu NLP Repository v6.10から、python-romkan_0.02-2nlp2~0edgy1.tar.gzをダウンロードして、01_MeCab_wrap_cxx.diffの書き換えを適応してみると(一つやはり私には理解できない警告が出たものの)インストールに成功したようである。さっそく、test.pyを試してみたら、SyntaxError: Non-ASCII character '\xc2' in file test.py on line 7, but no encoding declared;というエラーが出てしまうではないか。何なんだ、これは。そこで、
# vim: set fileencoding=utf-8 :というのを二行目に追加したらちゃんと動いた(私はvimを使うことが多いので、vim:と書いている)。
0.93
太郎    名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
この    連体詞,*,*,*,*,*,この,コノ,コノ
本      名詞,一般,*,*,*,*,本,ホン,ホン
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
二      名詞,数,*,*,*,*,二,ニ,ニ
郎      名詞,一般,*,*,*,*,郎,ロウ,ロー
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
見      動詞,自立,*,*,一段,連用形,見る,ミ,ミ
た      助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
女性    名詞,一般,*,*,*,*,女性,ジョセイ,ジョセイ
に      助詞,格助詞,一般,*,*,*,に,ニ,ニ
渡し    動詞,自立,*,*,五段・サ行,連用形,渡す,ワタシ,ワタシ
た      助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。      記号,句点,*,*,*,*,。,。,。
EOS
        BOS/EOS,*,*,*,*,*,*,*,*
太郎    名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
この    連体詞,*,*,*,*,*,この,コノ,コノ
本      名詞,一般,*,*,*,*,本,ホン,ホン
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
二      名詞,数,*,*,*,*,二,ニ,ニ
郎      名詞,一般,*,*,*,*,郎,ロウ,ロー
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
見      動詞,自立,*,*,一段,連用形,見る,ミ,ミ
た      助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
女性    名詞,一般,*,*,*,*,女性,ジョセイ,ジョセイ
に      助詞,格助詞,一般,*,*,*,に,ニ,ニ
渡し    動詞,自立,*,*,五段・サ行,連用形,渡す,ワタシ,ワタシ
た      助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。      記号,句点,*,*,*,*,。,。,。
        BOS/EOS,*,*,*,*,*,*,*,*
EOS
こんな結果になる。ニロウなんてことになっているけど。
その他、MySQL、Gnuplot、Rとの連携を確認。CGIスクリプトとしての動作を確認。
昨日は多次元配列の縦横の変換の手際よさに感嘆したのだが、今日は頻度一覧作成の手際悪さに落胆した。どうして、PHPのarray_count_valuesのようなのがないのだろう。私はPHPでこれを愛用しているので、ないと不便でかなわない。あと、連想配列のソートがPHPなら、キーでも値でも簡単にソートできるのだが、どうしてPythonではこんなに手間取るのか。Pythonが嫌いになってきた。新聞記事のコソボの話題から抜き出した以下の文にどんな単語がどれくらいの頻度で出現するかを集計するためにどうしたらいいか調べてみた。
#!/usr/bin/python
import re
sentences = "Earlier, Albanian police officers, part of Kosovo's\
multiethnic police force, were forced out of the neighboring \
Serbian village, where they were patrolling with fellow Serbs. \
It was the latest sign that Serbs in Kosovo, incensed by the \
declaration of independence Sunday, are trying to assert control \
over the northern part of Kosovo in an attempt to force partition."
noLine = re.sub('(\n)+', ' ', sentences)
sLine = re.sub('[.!:?]', ' ', noLine)
sLine2 = re.sub("[,\-']", ' ', sLine)
sLine3 = re.sub("[,';:]", '', sLine2)
sLine3 = re.sub('"', '', sLine3)
words = sLine3.split(' ')
words = [w for w in words if len(w) > 0]
wDict = {} 
for w in words:
    if wDict.has_key(w):
        wDict[w] += 1
    else:
        wDict[w] = 1
keys = wDict.keys()
keys.sort()
for key in keys:
  print key, wDict[key]
print 'There are a total of ' + str(len(wDict)) + ' words in this text.'
結果はこうなる。
Albanian 1 Earlier 1 It 1 Kosovo 3 Serbian 1 Serbs 2 Sunday 1 an 1 are 1 assert 1 attempt 1 by 1 control 1 declaration 1 fellow 1 force 2 forced 1 in 2 incensed 1 independence 1 latest 1 multiethnic 1 neighboring 1 northern 1 of 4 officers 1 out 1 over 1 part 2 partition 1 patrolling 1 police 2 s 1 sign 1 that 1 the 4 they 1 to 2 trying 1 village 1 was 1 were 2 where 1 with 1 There are a total of 44 words in this text.リストの中身を数え上げるのが面倒くさい。これはアルファベット順だが、頻度順にするにはどうしたらいいんだ?
#!/usr/bin/python
import re
sentences = "Earlier, Albanian police officers, part of Kosovo's\
multiethnic police force, were forced out of the neighboring \
Serbian village, where they were patrolling with fellow Serbs. \
It was the latest sign that Serbs in Kosovo, incensed by the \
declaration of independence Sunday, are trying to assert control \
over the northern part of Kosovo in an attempt to force partition."
noLine = re.sub('(\n)+', ' ', sentences)
sLine = re.sub('[.!:?]', ' ', noLine)
sLine2 = re.sub("[,\-']", ' ', sLine)
sLine3 = re.sub("[,';:]", '', sLine2)
sLine3 = re.sub('"', '', sLine3)
words = sLine3.split(' ')
words = [w for w in words if len(w) > 0]
wDict = {} 
for w in words:
    if wDict.has_key(w):
        wDict[w] += 1
    else:
        wDict[w] = 1
alph = sorted(wDict.items())
print 'Sorted by alphabet:'
for word in alph:
  print word[0], ":", word[1]
freq = sorted(wDict.items(), key=lambda (k, v): (v, k))
print 'Sorted by frequency:'
for word in freq:
  print word[0], ":", word[1] 
print 'There are a total of ' + str(len(wDict)) + ' words in this text.'
結果はこうなる。
Sorted by alphabet: Albanian : 1 Earlier : 1 It : 1 Kosovo : 3 Serbian : 1 Serbs : 2 Sunday : 1 an : 1 are : 1 assert : 1 attempt : 1 by : 1 control : 1 declaration : 1 fellow : 1 force : 2 forced : 1 in : 2 incensed : 1 independence : 1 latest : 1 multiethnic : 1 neighboring : 1 northern : 1 of : 4 officers : 1 out : 1 over : 1 part : 2 partition : 1 patrolling : 1 police : 2 s : 1 sign : 1 that : 1 the : 4 they : 1 to : 2 trying : 1 village : 1 was : 1 were : 2 where : 1 with : 1 Sorted by frequency: Albanian : 1 Earlier : 1 It : 1 Serbian : 1 Sunday : 1 an : 1 are : 1 assert : 1 attempt : 1 by : 1 control : 1 declaration : 1 fellow : 1 forced : 1 incensed : 1 independence : 1 latest : 1 multiethnic : 1 neighboring : 1 northern : 1 officers : 1 out : 1 over : 1 partition : 1 patrolling : 1 s : 1 sign : 1 that : 1 they : 1 trying : 1 village : 1 was : 1 where : 1 with : 1 Serbs : 2 force : 2 in : 2 part : 2 police : 2 to : 2 were : 2 Kosovo : 3 of : 4 the : 4 There are a total of 44 words in this text.これでアルファベット順と頻度順の一覧が出るが……おや、頻度順は少ない順ではないか。多い順にするにはどうしたらいいんだ? PHPなら簡単なのに。ちなみにPHPで同じことをさせてみるとこうなる。簡単ではないか。結果は同じなのでもう示さない。
<?php
sentences = "Earlier, Albanian police officers, part of Kosovo's\
multiethnic police force, were forced out of the neighboring \
Serbian village, where they were patrolling with fellow Serbs. \
It was the latest sign that Serbs in Kosovo, incensed by the \
declaration of independence Sunday, are trying to assert control \
over the northern part of Kosovo in an attempt to force partition."
$noLine = preg_replace("/(\n)+/", " ", $sentences);
$sLine = preg_replace("/[.!:?]/", " ", $noLine);
$sLine = preg_replace("/[,';:]/", "", $sLine);
$sLine = preg_replace("/\"/", "", $sLine);
$words = explode(" ",$sLine);
$wDict = array_count_values($words);
unset($wDict[""]);
ksort($wDict);
echo "Sorted by alphabet:\n";
foreach($wDict as $key=>$val){
  echo $key.":".$val."\n";
}
arsort($wDict);
echo "\nSorted by frequency:\n";
foreach($wDict as $key=>$val){
  echo $key.":".$val."\n";
}
echo "There are a total of ".count($wDict)." words in this text.\n";
?>
 今日参考にしたのは以下の2サイト。他にオライリーのPythonの本とか。 Pythonの練習をしてみようと思った。これまでどうしてもPHPしか使えなかったので、もう一つ別の言語を扱いたい。それにPHPはbioinformatics方面でちょっと弱い。BiorubyとかBioPython、BioPerl、BioJavaは充実しているが、BioPHPというのは(実はあるようなのだが)誰も知らない。
 しかし、勝手の違う言語はどうも戸惑うことばかり。だから、練習するんだが。
 まずはいつものように相補鎖を返させてみる。
import sys from Bio.Seq import Seq from Bio.Alphabet import IUPAC my_seq = Seq(sys.argv[1],IUPAC.unambiguous_dna) print my_seq.reverse_complement().tostring()こいつに実行権を与えて、seqrevとかいう名前でPATHの通ったところに保存し(revseqはEMBOSSで使われている)、seqrev ATGATGなどと打ってみれば、CATCATと返ってくる。
次にCSVファイルの行と列を入れ替えてみよう。PHPで大変な手間がかかった覚えがあるのだが、それが簡単にできるのに驚いた。
import sys
output = open('t_' + sys.argv[1], 'w')
input = open(sys.argv[1],'r')
lines = input.readlines()
wordlists = [line[:-1].split(',') for line in lines]
for row in zip(*wordlists):
  output.write((','.join(row)) + '\n')
output.close()
これだけか! これもtranscsvという名前で保存して、transcsv file.csvと打ってみると、t_file.csvというファイルができあがって、ちゃんと縦横が入れ替わっているではないか。もしかしたら、Pythonのことは好きになれるかも知れない。