DataFramesMeta, JuliaDB, Queryverseをそれぞれ触ってみた

Juliaでデータ処理

データ処理といえばR (& tidyverse)やPython (& Pandas)が思い浮かびますが、Juliaでも同様のデータ処理のパッケージが存在します。
今回はDataFramesMeta.jlJuliaDB, Queryverseのそれぞれで典型的な処理をやってみます。
tidyverseやPandasのような完成度はありませんが、簡単な処理だけでいいのであればこなせる印象です。

RとPythonで書いた例

まずはRとPythonで例を示します。
データセットはおなじみのirisを使います。

library(dplyr)
iris %>%
  dplyr::filter(Sepal.Length <= 6) %>%
  dplyr::group_by(Species) %>%
  dplyr::summarise(
    SumMeanSLandPL = mean(Sepal.Length) + mean(Petal.Length),
    stdSL = sd(Sepal.Length),
    stdPL = sd(Petal.Length)
  )

 # A tibble: 3 x 4
  Species    SumMeanSLandPL stdSL stdPL
  <fct>               <dbl> <dbl> <dbl>
1 setosa               6.47 0.352 0.174
2 versicolor           9.64 0.305 0.462
3 virginica           10.7  0.335 0.201

上のコードは「条件による行の選択(dplyr::filter)」、「集約と演算(dplyr::group_by, dplyr::summarise)」のを行う例となっています。

Pythonで同様な処理を行う例は以下です。(あまり美しくない書き方かも)

import pandas as pd
import seaborn as sns

iris = sns.load_dataset("iris")

df = iris[iris["sepal_length"] <= 6].groupby(
         "species",
          as_index = False
      ).agg(
          {"sepal_length":[np.mean, np.std],
           "petal_length":[np.mean, np.std]}
      ).assign(
          SumMeanSLandPL = lambda df: df[("sepal_length", "mean")] + df[("petal_length", "mean")]
      ).drop([("sepal_length", "mean"), ("petal_length", "mean")],
             axis=1
      ).rename(
          columns = {"sepal_length": "stdSL", "petal_length": "stdPL"}
      )

df.columns = df.columns.droplevel(1)
print(df)

      species     stdSL     stdPL  SumMeanSLandPL
0      setosa  0.352490  0.173664        6.468000
1  versicolor  0.305053  0.462141        9.636667
2   virginica  0.334581  0.200693       10.677778

Juliaで書いてみる

今回のJuliaの環境と、それぞれのパッケージのバージョンは以下の通りです。

つい先日v1.0.0がリリースされましたが、以下のコードがv1.0.0で動く保証は全くありません笑

julia> versioninfo()
Julia Version 0.6.2
Commit d386e40c17 (2017-12-13 18:08 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin14.5.0)
  CPU: Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz
  WORD_SIZE: 64
  BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell)
  LAPACK: libopenblas64_
  LIBM: libopenlibm
  LLVM: libLLVM-3.9.1 (ORCJIT, skylake)

julia> Pkg.status("DataFramesMeta")
 - DataFramesMeta                0.3.0

julia> Pkg.status("JuliaDB")
 - JuliaDB                       0.8.4

julia> Pkg.status("Queryverse")
 - Queryverse                    0.0.1

データセットのirisDataFrames.jlのパッケージに入ってるものをそのまま使います。

julia> using DataFrames, CSV

julia> filepath_iris = joinpath(Pkg.dir("DataFrames"), "test/data/iris.csv")

julia> df_iris = CSV.read(filepath_iris)

DataFramesMeta

DataFramesMeta.jlDataFrames.jlを、マクロを使って拡張するメタプログラミングツールです。

julia> using DataFramesMeta

julia> @linq df_iris |>
           where(:SepalLength .<= 6) |>
           by(:Species,
                 MeanSL = mean(:SepalLength), StdSL = std(:SepalLength),
                 MeanPL = mean(:PetalLength), StdPL = std(:PetalLength)) |>
           transform(SumMeanSLandPL = :MeanSL + :MeanPL) |>
           select(:Species, :SumMeanSLandPL, :StdSL, :StdPL) |>
           orderby(:SumMeanSLandPL)
3×4 DataFrames.DataFrame
│ Row │ Species    │ SumMeanSLandPL │ StdSL    │ StdPL    │
├─────┼────────────┼────────────────┼──────────┼──────────┤
│ 1   │ setosa     │ 6.468          │ 0.35249  │ 0.173664 │
│ 2   │ versicolor │ 9.63667        │ 0.305053 │ 0.462141 │
│ 3   │ virginica  │ 10.6778        │ 0.334581 │ 0.200693 │

JuliaDB

JuliaDBJulia Computingによって開発が進められているパッケージで、スパースなデータの取り扱いや分散処理の平易さあたりをウリにしているそうです。
ドキュメントが充実しているのはとてもありがたいです。

julia> using JuliaDB

julia> using Lazy

julia> filepath_iris = joinpath(Pkg.dir("DataFrames"), "test/data/iris.csv")

julia> tb_iris = loadtable(filepath_iris)

julia> @as t tb_iris begin
           filter(x -> x<=6, t, select = :SepalLength)
           JuliaDB.groupby(
               @NT(
                   meanSL = :SepalLength => mean,
                   stdSL = :SepalLength => std,
                   meanPL = :PetalLength => mean,
                   stdPL = :PetalLength => std),
               t, :Species)
           setcol(t, :SumMeanSLandPL, map(x -> x.meanSL + x.meanPL, t))
           select(t, (:Species, :SumMeanSLandPL, :stdPL, :stdSL))
           renamecol(t, :Species => :Class)
           sort(t, :SumMeanSLandPL)
       end
Table with 3 rows, 4 columns:
Class         SumMeanSLandPL  stdPL     stdSL
────────────────────────────────────────────────
"setosa"      6.468           0.173664  0.35249
"versicolor"  9.63667         0.462141  0.305053
"virginica"   10.6778         0.200693  0.334581

最後の@asLazyパッケージによる遅延評価を用いています。また、JuliaDBとLazyでgroupby関数がコンフリクトしているので上の例ではJuliaDB.groupbyと書いています。

Queryverse

Queryverseは1つのパッケージではなく、いくつかのパッケージをまとめたメタパッケージを指します。
Queryverseの詳細は作者のチュートリアル動画が詳しいです。

julia> using Queryverse

julia> filepath_iris = joinpath(Pkg.dir("DataFrames"), "test/data/iris.csv")

julia> df_iris = load(filepath_iris)

julia> df_iris |>
           @filter(_.SepalLength <= 6) |>
           @groupby(_.Species) |>
           @map({Species = (_..Species)[1],
               meanSL = mean(_..SepalLength),
               stdSL = sqrt.(var(_..SepalLength)),  # stdSL = std(_..SepalLength)だとなぜかエラー
               meanPL = mean(_..PetalLength),
               stdPL = sqrt.(var(_..PetalLength))  # stdPL = std(_..PetalLength)だとなぜかエラー
           }) |>
           @map({Species = _.Species,
               SumMeanSLandPL = _.meanSL + _.meanPL,
               stdSL = _.stdSL,
               stdPL = _.stdPL
               })
3x4 query result
Species    │ SumMeanSLandPL │ stdSL    │ stdPL
───────────┼────────────────┼──────────┼─────────
setosa     │ 6.468          │ 0.35249  │ 0.173664
versicolor │ 9.63667        │ 0.305053 │ 0.462141
virginica  │ 10.6778        │ 0.334581 │ 0.200693

QueryverseはDataFramesMetaと同様にパイプ|>を使ってデータを変形していくことができます。ただし、上記コードのコメントにもあるように@mapの中でstd()では動かずsqrt.(var())では動くというような謎な挙動が残っていたりします…。(将来的に修正されるといいのですが)

ちなみにここでは紹介していませんが、QueryverseにはDataVoyagerとの連携が組み込まれているので多様なデータ可視化が可能です。

おわりに

どのパッケージも一長一短で、この中のどれかがtidyverseやPandasのようなスタンダードになるにはまだまだ時間がかかるかと思います。
機能も限定的で、業務用途に耐えうるようなデータ分析基盤としてはJuliaはまだ厳しい印象です。

ただし、大規模なデータに対する高速な計算はJuliaの強みが活かされる部分だと思うので、(JITコンパイルの時間が探索的データ分析と相性が悪いとはいえ、)これからも注視していきたい分野です。
Juliaを数値計算に用いている人は少なくないはずなので、そういった数値計算の結果の集計等にうまく使っていくところがまずは第一歩になるのではないかと思います。

参考