Pyjuliaによるpythonからjulialangへの型変換まとめ
データ処理の高速化のためにPyjuliaを使ってpythonからjuliaの関数を使っています。本記事ではpyjuliaによって、pythonのデータ型がどのようなjuliaのデータ型に変換されるか、その対応表をまとめます。
juliaはpythonに比べて高速です。また、簡単に型を指定して固く書いたり、引数の型によって異なる関数定義をできるargument dispatcherなどpythonにはない便利な機能をたくさんもっています。デフォルトで型推論してくれるのでやたらと厳格なわけでもありません。非常にバランスのとれた言語だと思っています。
ただ、pythonと比べると型に注意を払う必要があります。pythonは型を意識する必要はほぼないですし、juliaは内部で固く書けます。なので、気にすべきはpythonからデータをわたすときです。Pyjuliaがどのようなデータ型の変換を行うか一望しておきたいと思います。
以下のようなデータ型がpythonからどのようなjuliaの型に変換されるかまとめます。
- basic type
- nested-list
- numpy.array
- unconvertable type (juliaに変換できない型)
環境構築
環境構築は、手前味噌ですが以下の記事でまとめたようにdockerでjupyter notebookを起動して、pythonとjuliaを動かします。
バージョンは以下です。
- python: 3.6
- julia: 1.3.0-rc3
import julia Base module
juliaのBase moduleを呼び出し、Base.typeof関数でpythonのデータ型がjuliaで何のデータ型に変換されているか確認します。
from julia import Julia jl = Julia(compiled_modules=False) from julia import Base
basic type
基本型一覧表
python | julia |
---|---|
int | Int64 |
bool | Bool |
str | String |
float | Float |
NoneType | Nothing |
dict | Dict{Any, Any} |
tuple | Tupe{Int or Float or ...} |
以下は注意が必要です。
- 辞書のkeyとvalueの型は推論されない (Anyになる)
- tupleは要素すべての型が指定される
出力例
Base.typeof(1) # <PyCall.jlwrap Int64> Base.typeof(True) # <PyCall.jlwrap Bool> Base.typeof("string") # <PyCall.jlwrap String> Base.typeof(2.0) # <PyCall.jlwrap Float64> Base.typeof(None) # <PyCall.jlwrap Nothing> Base.typeof({"a": 1}) # <PyCall.jlwrap Dict{Any,Any}> Base.typeof(tuple([1, 2])) # <PyCall.jlwrap Tuple{Int64,Int64}>
nested-list
ネストされたリスト一覧表
python | julia |
---|---|
n-th list (same length) | n-dim Array |
n-th list (distorted) | n-th nested Array |
この2つはイテレーションの挙動が異なるので注意が必要です。[(2次元以上のarrayのイテレーションの挙動がpythonと大きく異なる。公式doc)
違いは、出力例を見たほうが早いと思います。
出力例
intやfloatのテンソルになっているリストはN次元のArrayに変換されます。juliaではarrayのネストにはなっていないことに注意する必要があります。
same_len = [[1, 2], [3, 4]] Base.typeof(same_len) # <PyCall.jlwrap Array{Int64,2}>
いびつな形のネストされたリストはjuliaでもネストされたリスト (array)になります。
distorted_shape = [[1, 2], [3, 4, 5]] Base.typeof(distorted_shape) # <PyCall.jlwrap Array{Array{Int64,1},1}>
python側は区別が難しいです。 しかし、うまい処理的とは言い難いですが、numpy.arrayに変換してみると区別できます。
numpy.array
numpy.array一覧表
python | julia |
---|---|
numpy.array(n-th list (same length)) | n-dim Array |
numpy.array(n-th list (distorted)) | Array{PyObject, 1} |
numpy.arrayにも2種類の変換がなされることがわかります。これはnested-listの対応と1対1になっています。
出力例
intやfloatのテンソルになっているリストは、numpy.arrayに変換するとshapeメソッドで完全なリストのネスト構造がかえってきます。numpy.arrayのままjuliaにわたしても、N次元のArrayに変換されます。
np_same_len = np.array(same_len) np_same_len.shape # (2, 2) Base.typeof(np_same_len) # <PyCall.jlwrap Array{Int64,2}>
一方、いびつなネストされたリストはshapeメソッドで一部のネスト構造しかかえってきません。この違いによってjuliaにわたるデータ型の違いをpython側から判別できます。また、このnumpy.arrayはPyObject型の一次元arrayに変換されます。
np_distorted_shape = np.array(distorted_shape) np_distorted_shape.shape # (2,) Base.typeof(np_distorted_shape) # <PyCall.jlwrap Array{PyObject,1}>
unconvertable data type
table
python | julia |
---|---|
generator | PyObject |
zip | PyObject |
pandas.DataFrame | PyObject |
pandas.Series | PyObject |
上記のようにほとんどのobjectはPyObject型になります。juliaでPyObject型を使うメリットはよくわからないので、このあたりの型はjuliaにはわたさないほうが懸命だと思います。このあたりは素直にjuliaの関数の中でつくるほうが自然だと思います。
# generator Base.typeof((f for f in range(1))) # <PyCall.jlwrap PyObject>
# iterator Base.typeof(zip([1,], [1, ])) # <PyCall.jlwrap PyObject>
import pandas as pd Base.typeof(pd.DataFrame({"a": [1, 2]})) # <PyCall.jlwrap PyObject>
import pandas as pd Base.typeof(pd.Series( [1, 2])) # <PyCall.jlwrap PyObject>
まとめ
基本的に、気をつけるべきはネストされたリストだけなので、慣れてしまえばすぐに使えると思います。 juliaに書き直すだけで引くほど処理が速く(書き直すのも楽)なるのでどんどん使っていきたいです!