livedoorグルメのデータで機械学習してみる⑤

データクレンジングにあたり必要な機能のPythonでの実装方法の確認が一通り終わったので組み合わせてデータ変換プログラムを完成させる。

convert_ratings.py

# -*- coding: utf-8 -*-
 
import sys
 
if __name__ == "__main__":
    arg = sys.argv
 
    input_file = arg[1]
    output_file = arg[2]
 
    with open(input_file, 'r') as in_file:
        with open(output_file, 'w') as out_file:
            rows = 0
            for line in in_file:
                rows += 1

                # 先頭行はヘッダなのでスキップ
                if rows == 1:
                    continue

                list = line.split(",")

                restaurant_id = list[1]

                # ユーザーIDは16進->10進変換
                user_id = long(list[2], 16)

                total = list[3]

                out_file.write(str(user_id) + "," + restaurant_id + "," + total + "\n")

                if rows % 1000 == 0:
                    print(str(rows) + "行変換しました。")

            print(str(rows) + "行変換しました。")

まあ、こんな感じで。
では、ようやく変換を。

$ python convert_ratings.py ratings.csv ldgourmet_ratings_for_ibr2.csv
1000行変換しました。
2000行変換しました。
(省略)
205833行変換しました。

$ head ldgourmet_ratings_for_ibr2.csv
3993170538,310595,5
4240577537,10237,1
104934135,3334,2
104934135,15163,5
1290717597,567,3
1290717597,1026,5
1290717597,1058,5
1290717597,2569,3
1290717597,3309,4
1290717597,3648,4

バッチリ。
とりあえず今日はここまでにして、次回いよいよ協調フィルタリングに再挑戦。

カンマ区切り文字列の分割

ファイルの読み書きもできたし、16進を10進に変換できたし、いよいよMahout用データを作るプログラムを書こうと思ったら、カンマ区切りの入力データを分割する方法が未確認だということが発覚。調べて試してみる。

comma_split.py

# -*- coding: utf-8 -*-
 
if __name__ == "__main__":
    comma_del_str = "aaa,bbb,ccc,ddd"

    list = comma_del_str.split(",")

    for column in list:
        print(column)

文字列型のオブジェクトに対してsplit(“区切り文字”)するだけだった。

$ python comma_split.py
aaa
bbb
ccc
ddd

16進表記の文字列を数値に変換

いよいよメインイベント。
16進表記されたユーザーIDを数値型に変換する。

ちょっとGoogle先生に聞いてみたところ、なんとlong(“文字列”, 基数)だけでOKっぽい。
とりあえず対話型のインタプリタで試してみる。

>>> print(long("ee02f26a", 16))
3993170538
>>> print(long("fcc21401", 16))
4240577537

できちゃった。恐るべしPython。
メインイベントなのにHello Worldより短い。(笑)

Pythonでファイル入出力

続いてファイルの読み込みと書き出し。
とりあえずは入力データをそのまま書き出してみる。

fileio_sample.py

# -*- coding: utf-8 -*-

import sys

if __name__ == "__main__":
    arg = sys.argv

    input_file = arg[1]
    output_file = arg[2]

    with open(input_file, 'r') as in_file:
        with open(output_file, 'w') as out_file:
            for line in in_file:
                out_file.write(line)

ファイルを開くにはopen()という関数を使えばいいらしい。引数はいくつかあって、今回は「ファイル名,読み書きモード」というパターンを使用。ファイル名だけならモードが’r’扱いになるよう。あと、3番目にエンコーディングも指定可能。

11行目と12行目のwith文は説明が難しいけど初期処理とか終了処理とかを自動的にやってくれるものらしい。どんな初期処理や終了処理が動くかは関数によって違い、例えばファイルを開くopen関数の場合だと、withを抜ける時に終了処理として自動的にcloseしてくれるよう。

今回は1行読みながら1行書くので入力と出力の2つのファイルを同時にopenする。順番はどちらが先でもいいと思うが、何となく読む方を先に。

では動かしてみる。
livedoorグルメのデータはサイズが大きいので、テスト用に抜粋して使用。

$ head ratings.csv > test_input.csv
$ python fileio_sample.py test_input.csv test_output.csv
$ cat test_output.csv
id,restaurant_id,user_id,total,food,service,atmosphere,cost_performance,title,body,purpose,created_on
156445,310595,ee02f26a,5,0,0,0,0,,"名前は忘れましたが、札幌で食べたお店よりも、全然こっちの方が美味 しかったので、載せました。お店も綺麗(新規オープン・・)でランチは結構混んでいます。個人的にはゆったりと食事できるので夜の方がオススメです。  辛さが0倍から50倍まで選べるのもGOOD!、スープも2種類みたいで、友達は黄色がオススメと言っていましたが、自分は赤の方を食べました。かなり美味しかったです。店長も好感のもてるお兄さんでした。  駅近くなので一度お試しあれです!",0,"2006-10-07 05:06:09"
3842,10237,fcc21401,1,0,0,0,0,,"味的には別に取り立てておいしいと言うこともない。けどまずくもない。  ギリシア料理といもの自体があまり特徴がないということもあると思う。  でも★1を付けてしまったのはサービスの悪さから。  夜に行ったのですけど、何の説明もなくパンを出された。  本当はおかわり自由らしいけど、それについても何も言ってくれなかったので  当然こちらは催促しないし、「パンのおかわりいかがですか?」の一言もなかった。  女二人で料理3品とドリンク1つづつで¥7000ってかなり高いんじゃない?  場所はいいけど、かなり後悔。せめて笑顔と最低限の説明は欲しいところ。",0,"2004-10-20 00:34:28"
144379,3334,06412af7,2,0,0,0,0,,"意外と昔からあるお店で  近江牛、特選黒毛和牛など高級肉を扱ってるお 肉屋さん  ランチも、きっとおいしいと思い駐車場もあるので行ってみました  ステーキ焼ランチ1050円。とてもリーズナブル  炭火かなと思ったら、ガスでした    でてきたランチのお肉は、ロース肉  サシが入ってなく、見た目オージービーフっぽい  食べてみたら、うーんこれはやっぱ。。。  外国産のお肉みたいな味がする  高くておいしいのは当たり前  1000円ランチといえども肉の質は落として欲しくないな~と思った    他にもお客さん(サラリーマン系)が来てたけど  ランチでも、和牛サーロインステーキとか高い値段のを注文してました  ↑  焼き方を聞いていたので、和牛サーロインステーキ等の場合は  焼いてもらえるみたいですね    ",0,"2006-06-03 16:07:43"
144377,15163,06412af7,5,0,0,0,0,,"ランチがおすすめです  お肉自体の旨み、そしてあまみが  お口にじゅわぁ~と広がります。  あのお値段で、ここまでおいしかったお店は初めてです  駐車場は30台くらいとめられて  周りに別のお店はナイので、近くに看板もあるし場所もわかりやすい",0,"2006-06-03 15:14:45"
75967,567,4ceec99d,3,0,0,0,0,,"おいしいと言う友達のすすめでケーキを2種類、プリン系を2種類買ってみ ました。  ケーキはちょっと当たりハズレがあるのかな?  二つ買ったうちの一つ(苺がのったケーキ)はおいしかった。    プリンはクリームブリュレ風のと洋ナシがのったプリン。  両方とも超美味しい!!プリンはリピーター間違いなし。",0,"2004-12-01 23:12:29"
104898,1026,4ceec99d,5,0,0,0,0,,"ランチに利用しました。1,000円のカレー2種+ナンのセット。飲み物付   カレーがのったお皿にミニサラダとチキンも付いてました。    チキンカレーと野菜カレーを頼みました。カレーはスパイスが効いています。  でもだいぶ日本人向けに作ってあるように感じます。  私が頼んだチキン、野菜カレーはさらさらのタイプのカレーでは無く、少しとろみがあるタイプでした。  ナンが大きくて最初は食べきれるかな?と思ったのですが、全然いらぬ心配でした。  パリパリで、中はふっくらなナンはおかわりしたいぐらい美味しかったです。  夜のメニューを見せてもらったら、カレーはかなり種類があるみたいです。  激辛からマイルドと種類も豊富なので、辛いもの苦手な方も行ってみる価値ありです。",0,"2005-01-04 03:57:02"
86073,1058,4ceec99d,5,0,0,0,0,,"神楽坂付近に行く時は必ず寄る店です。  私はいつも抹茶ババロアを食べます。  甘さ控えめなババロア、上品な味の餡、濃厚な生クリーム  この3つがとってもいいバランス!!    注文してから待っている間に出るおせんべいも美味しい。  お茶をすすりながら待っている時間も堪能できる。  いつ行っても混んでいる理由は分かります。純粋においしいですもんね。    納得のお値段なんだけど‥  ただ持ち帰りにすると少し割高感を感じるんですよね。",0,"2004-11-09 00:34:17"
13968,2569,4ceec99d,3,0,0,0,0,,"タンタン麺を食べました。味は濃いめでしっかりとした味。  とっても美味しかったです。  お客さんはみんな地元の常連さんが多いと思いました。  店員の方も対応すごくいいです。",0,"2004-09-22 23:29:57"
97833,3309,4ceec99d,4,0,0,0,0,,"土曜日のランチで利用しました。  席が隣の人と近くてちょっとドキドキ。煙草吸う人来たら嫌だな~と。    ランチセットはメインが数種類から選べて、ピラフ、サラダ、デザート、コーヒーで1000円。  今回は+500円増のメインサイコロステーキを頂きました。  マスタードのソースとすごく合っていて美味しい。  連れのお皿からマッシュポテトを頂戴する。少し甘めで美味し~い!!    私が行った時は常に満員状態でした。  ‥にも関わらず、店員の方はよく気のきく感じでしたね。  コーヒーも気兼ねなくおかわりできますし。  また是非行きたいですね。",0,"2005-05-28 23:17:16"

今日のところはこのへんで。

はじめてのPython

Javaのpublic static void main()的なやつすら知らないので、まずはHelloWorldでも。
最終的にそこそこのステップがあるプログラムを作るので対話型ではなくモジュール(Pythonではプログラムを記述したファイルをこう呼ぶらしい)を作って実行。

helloworld.py

# -*- coding: utf-8 -*-

if __name__ == "__main__":
    print "Hello World."

1行目の「# -*- coding: utf-8 -*-」はソースのエンコード指定。上記ソースでは特に非ASCII文字が記述されているわけではないが、日本語を記述するときのためにおまじないのように書いておけばよさそう。

3行目の「if __name__ == “__main__”:」がJavaでいうところのmain()メソッドのようなもの。「__name__」という特殊な変数にはモジュール名(通常はファイル名の.pyの前の部分)が格納されるのだが、コマンドラインから実行された場合のみ「__main__」となり、このifブロックの中はコマンドラインから実行された場合のみ処理されることになる。

上記ソースの場合は他のモジュールにimportされるようなものではなくスクリプトとして実行(コマンドラインから動かすことをそう言うらしい)するためのものなので全ての処理をトップの階層に記述しても動きは同じだが、勉強なので何となく明示的に記述してみた。(mainなしでベタに書いたらbashのシェルスクリプトのようなイメージ。お気軽にやるならそれで全然いいと思う。)

実行してみる。

$ python helloworld.py
Hello World.

次に、コマンドライン引数を受け取る方法を試す。
とりあえず受け取った文字列をそのまま標準出力に表示。

cmdarg_sample.py

# -*- coding: utf-8 -*-

import sys

if __name__ == "__main__":
    arg = sys.argv

    print "input file = " + arg[1]
    print "output file = " + arg[2]

コマンドライン引数はsysというモジュールのargvという配列に格納されるらしい。sysを使うために3行目でimport。Javaの場合Systemクラスなどはimport不要だが、Pythonは何でもimportが必要なのかな。
引数の配列はarg[0]から始めたくなるところだが、arg[0]にはモジュールのファイル名が入ってくるよう。よって実質的にarg[1]から。(確かにpythonコマンドから見たらモジュールファイル名が第一引数か…)

$ python cmdarg_sample.py input.csv output.csv
input file = input.csv
output file = output.csv

とりあえず今日はここまで。

livedoorグルメのデータで機械学習してみる④

Mahoutに読み込ませるデータのユーザーIDをlong型にしなければならない。
Hiveの関数を見てみたが文字列を数値に変換できそうなものがなかったので別の手段を使うことに。
簡単なプログラム書くだけなので何でもいいのだけれど、せっかくなので勉強してみようと思っていたPythonを使ってみる。

開発環境でコマンドを叩いてみると幸いデフォルトでインストール済みのよう。CentOS6のデフォルトで2.6.6とちょっと古めだが、大したことをするわけではないので特に問題はないだろうと思ってバージョンアップはしないまま使う。

さて、必要なプログラムの要件はとりあえず以下のような感じ。
・コマンドライン引数で入力ファイルと出力ファイルのパスを受け取る
・入力ファイルを1行ずつ読み込み、必要な項目を抜き出してレイアウトを整え、出力ファイルに出力する
・ユーザーID(データを見た感じ16進表記の数値っぽい)はJavaのlong型に変換可能な数値にして出力

Python初体験で構文も全く知らないド素人なので一歩ずつ進める。
参考にしたのは↓こちら。(微妙にバージョン違うけど…)

Python 2.7ja1 documentation
Python-ism

続きは別記事にて。