2018/06/30

高速データ処理ツールnysolのMCMD(Mコマンド)を試してみる


というわけで早速使ってみる。
Windows上での環境構築については前回の記事を参照のこと。

環境


Windows 10(64bit)
CPU: Intel Core i7-7500U
メモリ: 16GB
Bash on Windows(Ubuntu 16.04.4 LTS (Xenial Xerus))

データ


MovieLens ml-20M(Harper and Konstan,2015*)を使う。
映画の評価情報が入っているデータセットである。
同梱のREADMEには

This dataset (ml-20m) describes 5-star rating and free-text tagging activity from [MovieLens](http://movielens.org), a movie recommendation service. It contains 20000263 ratings and 465564 tag applications across 27278 movies. These data were created by 138493 users between January 09, 1995 and March 31, 2015. This dataset was generated on March 31, 2015, and updated on October 17, 2016 to update links.csv and add genome-* files.
(邦訳筆者:)このデータセット(ml-20m)は映画推薦サービスMovieLens上の5段階評価のレイティングおよび自由記述によるタグ付け情報からなる。2,0000,263件の評価、27,278件の映画とそれらにタグ付けされた465,564のタグ情報を含む。データは1995/01/09~2015/03/31の期間、138,493人のユーザーのものを収集した。データセットは2015/03/31に出力され、2016/10/17にアップデートがなされた(links.csvおよびgenome-で始まるファイル群が付加された)

とある。

比較的大きめのデータセットで、解凍すると全体で約800MBにもなる。
nysolを試すのにはちょうどよいだろう。

ファイル構成はこんな感じ。

$ ls
genome-scores.csv  genome-tags.csv  links.csv  movies.csv  ratings.csv  README.txt  tags.csv

今回はこのうちmovie.csv(約1.3MB)およびratings.csv(約500MB)を使ってみる。
ちょっと中身を見てみると、moviesは各映画のIDとタイトル、ジャンルといった基本情報、

$ head movies.csv
movieId,title,genres
1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
2,Jumanji (1995),Adventure|Children|Fantasy
3,Grumpier Old Men (1995),Comedy|Romance
4,Waiting to Exhale (1995),Comedy|Drama|Romance
5,Father of the Bride Part II (1995),Comedy
6,Heat (1995),Action|Crime|Thriller
7,Sabrina (1995),Comedy|Romance
8,Tom and Huck (1995),Adventure|Children
9,Sudden Death (1995),Action

ratingsは評価したユーザのIDと映画のID、評点、および評価した時刻(unix秒)、

$ head ratings.csv
userId,movieId,rating,timestamp
1,2,3.5,1112486027
1,29,3.5,1112484676
1,32,3.5,1112484819
1,47,3.5,1112484727
1,50,3.5,1112484580
1,112,3.5,1094785740
1,151,4.0,1094785734
1,223,4.0,1112485573
1,253,4.0,1112484940

がそれぞれ入っている。


処理


nysolは複数のコマンド群からなる。
このうち基本的な処理をつかさどるのがMCMD(Mコマンド)パッケージである。
Mコマンドというだけあって、すべてのコマンドの頭にmの字がついている(ちなみにMは考案者の苗字にちなんでいるそうだ。)

各コマンドはパラメータiで入力ファイル名、パラメータoで出力ファイル名を指定する。
特に入出力ファイルの指定がなければ標準入出力を使う。

そのためパイプ処理的に次々とつなげて使うことができる。

$ {コマンド1} i={入力ファイル名} | {コマンド2} | ... | {コマンドn} o={出力ファイル名}

もちろん、パイプ処理で普通のunixコマンドに渡すこともできる。
パイプを活用し、並行処理をすることによって、CPUやメモリを効率的に扱える…らしい。
今回はml-20Mデータセットを使いつつ、MCMDを簡単に使ってみる。

mcut: 列を選択する

mcutは行から選択した列を抜き出すコマンド。

$ mcut f={項目名1},{項目名2}
※コンマの後には空白を入れない。

パラメータfで必要な列を選択する(fは何の略だろう…focus?)。
パイプラインに逐次データを流しこむ都合上、早い段階で必要な項目だけに絞り込んで流すデータ量を減らしたい。

movies内のIDおよびタイトルのみを切り出し、headコマンドに渡して先頭10行だけ出力してみる。

$ time mcut f=movieId,title i=movies.csv | head
movieId,title
1,Toy Story (1995)
2,Jumanji (1995)
3,Grumpier Old Men (1995)
4,Waiting to Exhale (1995)
5,Father of the Bride Part II (1995)
6,Heat (1995)
7,Sabrina (1995)
8,Tom and Huck (1995)
9,Sudden Death (1995)
#END# kgcut f=movieId,title i=movies.csv; IN=27278 OUT=27278; 2018/06/30 12:48:14

real    0m0.130s
user    0m0.000s
sys     0m0.047s
※行頭のtimeは時間計測用のコマンド

1MB程度のファイルだと一瞬で処理が終わるのがわかる。

mcount: 行を数える

mcountは行を数えるコマンド。
$ mcount  a={新しい列名} [k=数える対象の列]

パラメータaで集約した列の名前、パラメータkで数える対象の列を指定する(kはkeyとして、aはas?)。
kを指定しない場合は全部の行を数える。
なお、kで指定していない列の値は不定(適当なデータが選ばれる)なので当てにしない。

ユーザーが映画を評価したデータ(ratings.csv)を用いて、各映画の評価がどれだけ投稿されているかの回数を調べたい。そういうときにmcountが役に立つ。

$ time mcount a=count k=movieId i=ratings.csv | head
,count,movieId%0,rating,timestamp
,496951,4.5,1379744413
,2900510,4.0,843239650
,4115,100,3.0,942233644
,203,1000,4.0,868380324
,348,100003,2.0,1402337250
,1026,100006,2.5,1358891058
,6398,100008,4.0,1358823364
,2217,100010,3.0,1395061487
,50827,100013,2.5,1381595359
#END# kgcount a=count i=ratings.csv k=movieId; IN=20000263 OUT=26744; 2018/06/30 13:25:28

real    0m21.086s
user    0m19.391s
sys     0m4.297s

なんだか表示がおかしい…と思ったら、改行コードが悪さをしていた。
nysolで扱うcsvの改行コードはunix式(LF)のみとのこと。
このデータセットの改行コードはdos式(CRLF)なので、出力時に不具合が生じていたらしい。

そこでnkfなどのツールを用いて改行コードを変えてやる必要がある。
前回構築した環境ではnkfはデフォルトでは入っていなかったのでとりあえずインストールしてやる。

$ sudo apt-get install nkf
$ nkf -Lu ratings.csv > _ratings.csv
$ nkf -Lu movies.csv > _movies.csv

この新しいファイルでやってみると

$ time mcount a=count k=movieId i=_ratings.csv | head
userId,movieId%0,rating,timestamp,count
65891,1,4.0,965331158,49695
31323,10,4.0,1121207588,29005
91696,100,4.0,833287336,4115
21055,1000,4.0,940160814,203
19153,100003,5.0,1364238516,3
73026,100006,2.5,1358891058,1
21398,100008,4.0,1358823364,6
53478,100010,4.0,1394292232,22
134567,100013,3.5,1418289706,5
#END# kgcount a=count i=_ratings.csv k=movieId; IN=20000263 OUT=26744; 2018/06/30 13:27:06

real    0m27.944s
user    0m24.281s
sys     0m5.203s

綺麗に出力された。

さてここでID(movieId)および評価回数(count)以外の列の値は不定なため意味のある値でない。そこでmcutと組み合わせてやれば、

$ time mcut f=movieId i=_ratings.csv | mcount a=count k=movieId | head
#END# kgcut f=movieId i=_ratings.csv; IN=20000263 OUT=20000263; 2018/06/30 16:09:02
movieId%0,count
1,49695
10,29005
100,4115
1000,203
100003,3
100006,1
100008,6
100010,22
100013,5
#END# kgcount a=count k=movieId; IN=20000263 OUT=26744; 2018/06/30 16:09:15

real    0m22.724s
user    0m21.688s
sys     0m4.109s

となり不要な列を削除して表示できる。

mbest: ソートして指定の行数を返す

せっかく集計したので評価回数の多い映画のリストを出力したい。
そこでmbestコマンドを使って上位のレコードを取得する。

$ mbest s={ソートする列名}[並べ替えオプション] size={取得する行数}

パラメータsでソートする列名を指定する(sはsortだろうか)、
並べ替えは、デフォルトでは文字列・昇順で行われるが、
項目名の後にオプションをつけることで方式を指定できる。

%r ...文字列・降順
%n ...数値・昇順
%nr ...数値・降順

(n がnumericとして、r はreversed?)
ここでは評価回数について降順(大きい方から順に)でデータを取得したいので、並べ替えオプションには%nrと指定してやる

$ time mcut f=movieId i=_ratings.csv | mcount a=count k=movieId | mbest s=count%nr size=10
#END# kgcut f=movieId i=_ratings.csv; IN=20000263 OUT=20000263; 2018/06/30 16:13:13
#END# kgcount a=count k=movieId; IN=20000263 OUT=26744; 2018/06/30 16:13:25
movieId,count%0nr
296,67310
356,66172
318,63366
593,63299
480,59715
260,54502
110,53769
589,52244
2571,51334
527,50054
#END# kgbest s=count%nr size=10; IN=11 OUT=10; 2018/06/30 16:13:25

real    0m22.447s
user    0m19.797s
sys     0m3.594s
(mbestで出力するサイズを指定できるためheadに渡す必要がなくなった。)

mjoin: データを結合する

さて、上位の件数が求まったところで、映画のIDだけではこの映画が何かを知ることはできない。そこでmjoinを使い、映画のIDとタイトルが紐付いたファイル(movies.csv)からタイトルのデータを引っ張ってくることにする。
mjoinコマンドはデータを結合するコマンド。

$ (何らかの処理) | mjoin k={統合に使うキー} i={読み込むファイル名}

受け取ったデータをキーをもとに統合する。これを使えば、

$ time mcut f=movieId i=_ratings.csv |mcount a=count k=movieId |mbest s=count%nr size=10|mjoin k=movieId i=_movies.csv
#END# kgcut f=movieId i=_ratings.csv; IN=20000263 OUT=20000263; 2018/06/30 16:23:32
#END# kgcount a=count k=movieId; IN=20000263 OUT=26744; 2018/06/30 16:23:43
#END# kgbest s=count%nr size=10; IN=11 OUT=10; 2018/06/30 16:23:43
movieId%0,title,genres,count
110,Braveheart (1995),Action|Drama|War,53769
2571,"Matrix, The (1999)",Action|Sci-Fi|Thriller,51334
260,Star Wars: Episode IV - A New Hope (1977),Action|Adventure|Sci-Fi,54502
296,Pulp Fiction (1994),Comedy|Crime|Drama|Thriller,67310
318,"Shawshank Redemption, The (1994)",Crime|Drama,63366
356,Forrest Gump (1994),Comedy|Drama|Romance|War,66172
480,Jurassic Park (1993),Action|Adventure|Sci-Fi|Thriller,59715
527,Schindler's List (1993),Drama|War,50054
589,Terminator 2: Judgment Day (1991),Action|Sci-Fi,52244
593,"Silence of the Lambs, The (1991)",Crime|Horror|Thriller,63299
#END# kgjoin i=_movies.csv k=movieId; IN=27278 OUT=10; 2018/06/30 16:23:43

real    0m20.631s
user    0m17.438s
sys     0m3.391s

タイトルを引っ張ってくることができる。
こいつを多少整形してやると、

$ time mcut f=movieId i=_ratings.csv |mcount a=count k=movieId |mbest s=count%nr size=10|mjoin k=movieId i=_movies.csv | mcut f=count,title | msortf f=count%nr
#END# kgcut f=movieId i=_ratings.csv; IN=20000263 OUT=20000263; 2018/06/30 16:34:39
#END# kgcount a=count k=movieId; IN=20000263 OUT=26744; 2018/06/30 16:34:50
#END# kgbest s=count%nr size=10; IN=11 OUT=10; 2018/06/30 16:34:50
#END# kgjoin i=_movies.csv k=movieId; IN=27278 OUT=10; 2018/06/30 16:34:51
#END# kgcut f=count,title; IN=10 OUT=10; 2018/06/30 16:34:51
count%0nr,title
67310,Pulp Fiction (1994)
66172,Forrest Gump (1994)
63366,"Shawshank Redemption, The (1994)"
63299,"Silence of the Lambs, The (1991)"
59715,Jurassic Park (1993)
54502,Star Wars: Episode IV - A New Hope (1977)
53769,Braveheart (1995)
52244,Terminator 2: Judgment Day (1991)
51334,"Matrix, The (1999)"
50054,Schindler's List (1993)
#END# kgsortf f=count%nr; IN=10 OUT=10; 2018/06/30 16:34:51

real    0m21.086s
user    0m17.641s
sys     0m3.766s

これでサイト上で評価回数の多かった映画10件の評価数、ならびにそのタイトルが出力できた。
こんな感じで比較的高速にデータを処理できる。

後記

今回はデモだったので中間処理ファイルを保存せずにいちいちコマンドを書いていたが、
ある程度何度も使うような処理結果に関しては途中経過を適宜ファイルに保存しておくとよい。

また、コマンドの処理結果を試すのにいちいち全ファイルを読み込むのも煩雑なので、
色々試したいときは少量の行数を読み込んで作業するといいだろう。
例えばheadコマンドでデータ読み込んでパイプで渡してやるとサクサク動くのでよい。

$ head {ファイル名} | {mコマンド}



ハマった点は、改行コード以外ではmcountコマンド。mcountではソートされた結果に基づいて行をカウントしているので、コマンド実行時にqオプション(自動ソートにしない)をつけると正常にカウントされない。特に警告も出ないので、原因解明に時間がかかった。

今のところWeb上にそんなに情報もないので、利用者が増えて知見が蓄積されていくことを期待したいところ。

引用文献


* F. Maxwell Harper and Joseph A. Konstan. 2015. The MovieLens Datasets: History and Context. ACM Transactions on Interactive Intelligent Systems (TiiS) 5, 4, Article 19 (December 2015), 19 pages. DOI=<http://dx.doi.org/10.1145/2827872>


2018/06/29

高速データ処理ツールnysolをBash on Windows (Subsystem for Linux)で動かす

要約


データ処理ツールnysolをWindows PCで動かすまで。

環境 


Microsoft Windows 10(64bit)


経緯


コマンドライン上でcsv形式のデータを高速に処理できるnysolなるものがあるという。


NYSOL(にそる)はデータ分析のためのオープンソースソフトウェアで,2003 年にリリースされたMUSASHIの後継となる.NYSOL の特徴は,大規模なCSV データに対して単一の処理に特化したコマンド群を組み合わせることで,データの加工・前処理から,マイニングアルゴリズムの適用まで,KDD (Knowledge Discovery in Databases) の全プロセスを効率よく実現できる.そして1 億件以上の大規模データをPC で処理することが可能である.
中原・中本・羽室(2016)*

気になる。

しかし公式ドキュメントを読んだところ、残念ながらWindows上ではそのままでは動かない。
Windows上で動かす選択肢としては

1. Virtual box上で動かす
2. Cygwin上で動かす
3. Bash on Windows(Windows Subsystem for Linux)上で動かす

があるが、わざわざVirtual Boxを立ち上げて動かすのは面倒。

手軽に動かしたいので、既に入っているGit Bashで動かそうと挑戦したものの、
そちらは諸々のライブラリの入れ方がわからず失敗。

そこでBash on Windows(Windows Subsystem for Linux)を導入し、インストールすることにした。
これを使えば、コマンドプロンプトからUbuntu上のbashに簡単に遷移できる。

手順


Bash on Windowsの導入は以下のリンクを参考にした。

Windows Subsystem for Linuxをインストールしてみよう!

これでUbuntuが入るところまではできるので、今度は公式ページを参考にする

インストール - NYSOL

今回入れたのはUbuntuなので「Ubuntu Linux」の項を見ればよい。
公式ページ経由で最新版(今回は2.4)のインストール用のファイルをダウンロードする。

nysol_2.4-0_amd64.deb

をダウンロード。

これをそのままインストールしようとしたらgemがないと言われて怒られるので、rbenvを入れる(こだわりがなければ普通のrubyでもいいかもしれない)。

$ sudo apt-get install rbenv

rbenvが入った段階でnysolがインストール可能になる。

$ sudo dpkg -i nysol_2.4-0_amd64.deb

インストールが完了したらnysolのコマンドが動くかどうか確認する。
$ mcut -version
lib Version 2:1:0:0: mod Vesion 2773f52145b34ff52ac0ec5a7181496380fa3dca

これでnysolが使える状況にはなったようだ。
とりあえず今日はここまで。

次回はこれを使ってデータの加工に取り組みたい。

引用文献


* 中原 孝信・中元 政一・羽室 行信(2016). ビッグデータ解析ツールNYSOL
―性能評価,並列処理,ビジネス応用ケース―. オペレーションズ・リサーチ 61(1), 11-18.

2018/06/25

分析のガイドライン


院生だった頃に学部生の指導用に作った資料をSlideShareにて公開しました。
データ収集&クリーニングが終わってさあ分析だ、となった際にそこからどういう流れで展開するのかについて軽くまとめたものです。

多少背景を説明すると、私がTAをしていた実習(自分たちで仮説を立ててデータ収集・分析からレポート作成までやるという内容)の補助資料としてこういうものを作りました。

レベルとしては、座学で一応入門レベルの統計学の知識を学んだものの実データでの分析はやったことがない、という程度を想定しています。

実データを解析するにあたって意外と取りこぼされそうなところ(再現性&効率性の重要さ)を強調して書きましたが、多少煩雑かもしれません。一方で、統計分析の細かいところについては割愛しています。

こういった資料を眠らせたままにしておくのももったいないので、今後折をみて世に出していきたいです。



2018/03/11

複数のcsv/tsvをまとめてひとつのExcelファイルにしたい[python]


複数の分析結果をcsvで出力したりしたものをひとつにまとめたくなったのでメモ。


import pandas as pd
import os
import re
import openpyxl as opx

# tsvの入っているフォルダの名前
folder_name = 'folder'

# フォルダのリストを列挙
files =  os.listdir(folder_name)
# 拡張子がtsvのものだけを抜き出す + ファイルのパスを付加
files = [folder_name + x for x in files if re.search(r'\.tsv$', x)]

# 出力ファイル名
filename = 'output.xlsx'

# エクセルのシートに各ファイルを格納
EXL =pd.ExcelWriter(filename, engine='openpyxl')
for filepath in files:
    sheet = pd.read_csv(filepath, sep='\t')
    sheet.to_excel(EXL, sheet_name=filepath.replace(folder_name, ''), index=False)

# 保存
EXL.save()

2018/02/26

An Illustrated Guide to Theoretical Biology ch01レジュメ

勉強会で発表した資料。

テキストは
Case(1999) An Illustrated Guide to Theoretical Biology

発表した第一章”Exponential and Geometric Population Growth”は
個体群動態を考える基礎となる集団の指数的成長・幾何級数的成長を扱っている。

pdf