Python(+Pandas), R, Julia(+DataFrames) でのテキストファイルおよび SQLite からの読み込み

  • 6
    いいね
  • 0
    コメント

2017/1/3 追記: DataFrames.jl はAPIの互換性も一部失われる大規模な改修を行った v.0.9 のリリースを2017年の2月に予定しているそうです。この記事は v.0.6.10に基づいて書かれているので注意してください。時間が出来てなおかつ気が向いたら(おい)それに合わせて書き直すかもしれません。
ただ、ここのところあまり Julia を触る時間もなくて……。


「気軽に書きましょう!」というお言葉に甘えて、本当に興味あるから触ってみたレベルですが Julia Advent Calendar 2015 のための記事として投稿させていただきます。

この記事では Julia でのデータの整形・基本的な集計処理について、(私が普段用いている)Python の Pandas ライブラリや R と比較しながら書いていきます。……という予定だったのですが、書いているとデータ読み込みのところまでで時間が無くなってしまったという情けない状況です。また続きを書く予定ですのでご容赦ください。

サンプルのデータ・セットとしては以下に公開されている Million Songs Dataset (Thierry Bertin-Mahieux et al., 2011.)の Subset を使います。

http://labrosa.ee.columbia.edu/millionsong/pages/getting-dataset#subset

subsetとはいえ結構な大きさのこのデータをダウンロードされるという方がいらっしゃいましたら申し訳ございませんが、この記事の中で使うのはこのデータセットのごく一部分で、AdditionalDataset ディレクトリ内にある以下の2つです:

  1. subset_artist_term.db
  2. subset_unique_artists.txt

1. のファイルは SQLite DB で、2. はテキスト形式です。
元のページの記述を読むと2.のファイルは"artist id<SEP>artist mbid<SEP>track id<SEP>artist name" という形式で記述されているという旨があります。このファイルをとりあえず artist ファイルを head で見てみますと以下のようになっています。

AR009211187B989185<SEP>9dfe78a6-6d91-454e-9b95-9d7722cbc476<SEP>TRAWSCW12903CD6C7E<SEP>Carroll Thompson
AR00A6H1187FB5402A<SEP>312c14d9-7897-4608-944a-c5b1c76ae682<SEP>TRAKWGL12903CB8529<SEP>The Meatmen
AR00LNI1187FB444A5<SEP>7e836d29-fc2d-4a1f-b8da-566d47c49eed<SEP>TRAHVYQ128F429826B<SEP>Bruce BecVar
AR00MBZ1187B9B5DB1<SEP>ff748426-8873-4725-bdc7-c2b18b510d41<SEP>TRAFCPP128F426EC01<SEP>Memphis Minnie
AR01IP11187B9AF5D2<SEP>dbd2ebce-623d-4639-946e-c558bf56a0e3<SEP>TRAZNKG12903CDCF8A<SEP>Call To Preserve
AR01VU31187B997DA0<SEP>103241b0-6adf-4b4f-9cff-5c87459f61a4<SEP>TRAKZMB128F427B44F<SEP>Grand Funk
AR01W2D1187FB5912F<SEP>125948ec-7f91-4d1a-8b83-accbf50fae3d<SEP>TRASHYD128F93119EE<SEP>3OH!3
AR022JO1187B99587B<SEP>9538ab80-dcbf-4b94-a0cf-e5d1fbbc42c9<SEP>TRAHSJR12903CBF093<SEP>Ross
AR02DB61187B9A0B5E<SEP>bf035517-124e-409b-90f5-35618081a332<SEP>TRBANKK12903CDA071<SEP>Annie Philippe
AR02IU11187FB513F2<SEP>f19ad155-d809-4770-ab8d-7579467d9f55<SEP>TRAJFCC12903CC55AD<SEP>Tito Puente

ファイル形式について見ますと、description通りに区切り文字列として<SEP>が用いられていて、ヘッダはありませんね。
R の read.delim と Julia の DataFrames.readtable は複数文字の区切り文字に対応していませんので、適当に置換を行う必要があります。コンマはアーティスト名などに使われている可能性がありますので、タブが無難そうです。R と Julia では<SEP>をタブ置換したものを読み込むことにします。

ついでに書きますと、このファイルの224行目には Buzzov¬"en というアーティスト名がありまして、このようにダブルクオートが一文字だけあるようなケースで Pandas は勝手に無視して読んでくれちゃうようですが、それ以外ではエラーが起きて読み込めませんので、今回はこのダブルクオートは削除することにしました。

Python+Pandasでのデータ読み込みは以下です。

import pandas
import sqlite3


conn = sqlite3.connect("subset_artist_term.db")
artist_term = pandas.read_sql(
    "SELECT * FROM ARTIST_TERM",
    conn)
unique_artists = pandas.read_csv(
    "./subset_unique_artists.txt",
    names=["artist_id", "artist_mbid", "track_id", "artist_name"],
    sep="<SEP>")

R ですとこんな感じになります。

require(RSQLite)

conn <- dbConnect(SQLite(), "subset_artist_term.db")
artist_term <- dbReadTable(conn, "artist_term")

unique_artists <- read.delim(
    "subset_unique_artists.tsv",
    sep="\t",
    header=F,
    col.names=c("artist_id", "artist_mbid", "track_id", "artist_name"))

で、Julia 版。

import DataFrames
unique_artists = DataFrames.readtable(
    "subset_unique_artists.tsv",
    header=false,
    names=[:artist_id, :artist_mbid, :track_id, :artist_name])

以上のコードで TSV を読み込むまでは良かったのですが、SQLite の読み込みで詰まりました。SQLite.jl は DataStreams.Data.Table 形式で読み込むので、DataFrame への変換が必要になります。
変換の方法については julia-users 内での次のやり取りが参考になりました(そのままだと動きませんでしたが) https://groups.google.com/forum/#!msg/julia-users/IFkPso4JUac/KwYj91fJAwAJ
SQLite からの DataFrame でのデータ読み込みは以下のようにすると出来ました。

import SQLite
import DataStreams

function DataFrame(dt::DataStreams.Data.Table)
    cols = dt.schema.cols
    data = Array(Any,cols)
    types = dt.schema.types
    for i = 1:cols
        data[i] = dt.data[i]
    end
    return DataFrames.DataFrame(data,Symbol[symbol(x) for x in dt.schema.header]) 
end

conn =  SQLite.DB("subset_artist_term.db")
artist_term_table = SQLite.query(conn, "SELECT * FROM ARTIST_TERM")
artist_term = DataFrame(artist_term_table)

2015/12/18 追記:

上記のコードだと各列が Nullable{UTF8Strings} 型になりますが、それよりも NA を使って表現したほうが良さそうな感じがしてきました。

ですので、

function DataFrame(dt::DataStreams.Data.Table)
    cols = dt.schema.cols
    data = Array(Any,cols)
    types = dt.schema.types
    for i = 1:cols
        data[i] = [isnull(el)? NA : get(el) for el in dt.data[i]]
    end
    return DataFrames.DataFrame(data, Symbol[symbol(x) for x in dt.schema.header]) 
end

としたほうが良いかもしれません。


とりあえず今回はここまでです。スライス処理・送別集計などの比較をまた書く予定です。

動作確認は以下の環境で行いました:

  • OS: Manjaro Linux
  • Python: 3.5.0
    • pandas: 0.17.1
  • R: 3.2.2
    • RSQLite 1.0.0
  • Julia: 0.4.0
    • DataFrames: 0.6.10
    • DataStreams: 0.0.3
    • SQLite: 0.3.0

修正:

  • Julia のコードで SQLite.connect と書いていましたが SQLite.DB の間違いでした。(2015/12/16)