cucumber flesh

Rを中心としたデータ分析・統計解析らへんの話題をしていくだけ

Rおじさん、Pythonistaになる

こちらをご覧ください。踏み絵ではありません。R上で地理空間データを扱うPythonモジュール、geopandasによる作図を行なっている画面です。

f:id:u_ribo:20170716094903p:plain

え、RでPythonを!?と驚かれる方もいるかもしれませんが、reticulateというRパッケージを使うことで、ほぼストレスフリーでPythonのモジュールや関数がR上で利用可能になります。先の図は次のコードによって実行されました。

library(reticulate)
# モジュールの呼び出し
gpd <- import("geopandas")
plt <- import("matplotlib.pyplot")
# サンプルデータの読み込み
world <- gpd$read_file(gpd$datasets$get_path("naturalearth_lowres"))
# データセットの確認 head(world)
# ではないので注意
world$head(n = 3L)
# 作図と出力
world$plot()
plt$show()

reticulateはRStudio社が中心となり開発されている、Pythonのモジュール、クラス、関数をRで利用可能にするRのパッケージです。

R Interface to Python • reticulate

CRAN - Package reticulate

先ほどのコードを振り返りながら、reticulateでできることを見て行きましょう。参考のためPythonでのコードを併記します。

import geopandas as gpd

world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
world.head(3)
world.plot()

Rで言う所のライブラリ(パッケージ)、Pythonモジュールはimport()関数を使って呼び出します。Pythonと同じ関数になっているのでわかりやすいですね。

gpd <- import("geopandas")
# Rのclass()でクラスを確認。Pythonモジュールとして扱われている

class(gpd)
# [1] 'python.builtin.module'
# 'python.builtin.object'

読み込んだモジュールの関数を使うにはモジュールオブジェクト中で$演算子を使って参照します。これによりgeopandasモジュールのサンプルデータをオブジェクトとして利用可能にします。次のコードの最初2行を見る通り、Python.をRでの参照機能をもつ$演算子に置換しているだけです。これはとてもわかりやすいと思います。

world <- gpd$read_file(gpd$datasets$get_path("naturalearth_lowres"))

world$head(n = 3L)
# continent gdp_md_est geometry \ 0 Asia
# 22270.0 POLYGON ((61.21081709172574
# 35.65007233330923,...  1 Africa
# 110300.0 (POLYGON ((16.32652835456705
# -5.87747039146621...  2 Europe 21810.0
# POLYGON ((20.59024743010491
# 41.85540416113361,...  iso_a3 name
# pop_est 0 AFG Afghanistan 28400000.0 1
# AGO Angola 12799293.0 2 ALB Albania
# 3639453.0

# パイプ演算子だって使えちゃう world <-
# gpd$datasets$get_path('naturalearth_lowres')
# %>% gpd$read_file() world$tail(n = 3L)

関数内の引数はR同様、括弧の中で引数名と値の組み合わせを指定します。

world$plot()

普段慣れている$演算子による操作が行えるので、Pythonも怖くないですね。さらにRStudioを使っていると下の図のように、入力補完やドキュメントの内容(引数含めて)を表示してくれるので、Pythonに慣れていない私のような人間でも安心ですね。学習が捗りそうです。

f:id:u_ribo:20170716095922p:plain

しっかりとドキュメントを読みたいときは、py_help()を使います。ここでもRStudioを使っていると、ドキュメントの参照が手軽で良いですね。

py_help(world$tail)

Python環境の切り替え

Pythonを使っていて悩ましい問題の一つが、2系と3系を始めとした複数環境の切り替えです。reticulateではuse_python()を始めとして、condaや仮想環境下のdocker(bhaskarvkさんの野心的なプロジェクトを参照)でのPythonも実行できるようになっています。例を見てみましょう。

実際のPython環境の切り替えは、Rのセッション中にimport()が実行されたタイミングで行われます。そのため次のコードは一度Rセッションを終了してから再度実行しているものになります。

sys <- import("sys")
sys$version
# [1] '3.6.1 |Anaconda 4.4.0 (x86_64)|
# (default, May 11 2017, 13:04:09)
# \n[GCC 4.2.1 Compatible Apple LLVM 6.0
# (clang-600.0.57)]'

library(reticulate)
use_python("/usr/bin/python")
sys <- import("sys")
sys$version
# [1] 2.7.13 (default, Jul 15 2017,
# 12:14:18) \n[GCC 4.2.1 Compatible
# Apple LLVM 8.1.0 (clang-802.0.42)]

library(reticulate)
use_condaenv("py36con", conda = "/Users/uri/.pyenv/versions/anaconda3-4.4.0/bin/conda", 
    required = TRUE)
sys <- import("sys")
sys$version
# [1] '3.6.1 |Anaconda 4.4.0 (x86_64)|
# (default, May 11 2017, 13:04:09)
# \n[GCC 4.2.1 Compatible Apple LLVM 6.0
# (clang-600.0.57)]'

どの環境を利用するかには優先順位があるようで、use_*()により指定された環境、Sys.setenv(RETICULATE_PYTHON)でのパス、Sys.which('python')により見つかるパス、慣用的にPythonが配置されるパスだそうです。都度use_*()を実行するのも手間なので、Sys.setenv()に値を記述しておくと良いかもしれません。なお、RStudioでのSys.which('python')の参照先は2系になるらしいので注意です(それでハマった)。

その他にも

reticulateパッケージの良いところは他にもあって、

  • データ型の変換もいい感じに扱ってくれる。
    • タプルや辞書の操作のために専用の関数が用意されている。
  • PythonスクリプトをRで実行、テキストからPythonスクリプトを実行

が代表的な機能です。

RodeoやらPyCharmやら色々とPythonの開発環境を試しましたが、RStudioに慣れすぎていてRStudio依存になってしまっているのでreticulate、すごく助かります。

より詳しいことはRStudioがvignettesを書いているのでそちらを見ていただきたいのですが、nakamichiさんが翻訳してくださっているのでそちらも是非!ちなみにですがnakamichiさんは他にもたくさんのvignettesを多数翻訳していらっしゃいます。

qiita.com

少しずつですが、reticulateによるPythonの利用を前提にしたRパッケージも増えてきました (例 docker, tensorflow, , RQGIS)。今後ますます、RとPythonの連携による実現領域が広がっていくことが期待できますね。Pythonとのインターフェイスを用意してくれるRStudio最高!PythonとR仲良くしようね!

Enjoy!!

データフレームの変数を正しく扱うためにreadr::type_convert関数やreadr::parse関数群を使う

Rをやっていると時々、データ型の違いによる関数実行の失敗や不正確な結果の出力が起こる。errorで処理が停止したりwarningで間違っていることがわかると良いのだけど、間違ったデータ型で処理が通ってしまって、結果を見て「あれへんだな」ということがあると良くない。

(自分の中で)ありがちなのが、日付・時間型で扱いたい変数が文字列となっているような、うまくパースできていないという状況。うっかり日付・時間型のデータを入力に用いる関数に投げるとエラーになったりする。

library(tidyverse)
# 例のために文字列で「日付・時間」のデータを用意
(d <- data_frame(
  datetime = c("2017-07-12 08:30:11",
               "2017-07-12 09:10:36",
               "2017-07-12 10:43:42")
))
## # A tibble: 3 x 1
##              datetime
##                 <chr>
## 1 2017-07-12 08:30:11
## 2 2017-07-12 09:10:36
## 3 2017-07-12 10:43:42

日付・時間型として正しく扱うため、lubridate::as_datetime() (baseの関数であればas.POSIXct()か)を使えば良いのだけど、

d %>% mutate(
  datetime = lubridate::as_datetime(datetime)
)
## # A tibble: 3 x 1
##              datetime
##                <dttm>
## 1 2017-07-11 23:30:11
## 2 2017-07-12 00:10:36
## 3 2017-07-12 01:43:42

と書くのがちょっとだるい。そんな時には表題のreadrパッケージの関数type_convert()を使う。

# readrパッケージはtidyverseに含まれる
d %>% 
  type_convert()
## # A tibble: 3 x 1
##              datetime
##                <dttm>
## 1 2017-07-12 08:30:11
## 2 2017-07-12 09:10:36
## 3 2017-07-12 10:43:42

楽。

この関数は何をしているかというと、文字列として扱われているデータフレームの変数のデータ型をよしなに変換してくれるというもの(utilsにあるtype.convert()とは別物。話がそれるがこの関数の変換はあまりよろしくない気がする…)。

今回のように、本来、日付・時間や数値であるはずの変数が文字列になっている場合に使うと良いだろう。

また、readrにはベクトルを入力として、データ型をパースし直すparse_*関数群が備わっている。これはこれで便利。

d$datetime %>% parse_datetime()
## [1] "2017-07-12 08:30:11 UTC" "2017-07-12 09:10:36 UTC"
## [3] "2017-07-12 10:43:42 UTC"
d$datetime %>% parse_guess()
## [1] "2017-07-12 08:30:11 UTC" "2017-07-12 09:10:36 UTC"
## [3] "2017-07-12 10:43:42 UTC"
parse_datetime("2017年7月12日 9時20分", "%Y年%m月%d日 %H時%M分") 
## [1] "2017-07-12 09:20:00 UTC"
parse_date("2017-7-12", "%Y-%m-%d")
## [1] "2017-07-12"

readrパッケージに関しては

qiita.com

も参考までに。

Kaggleでも利用されている高圧縮ファイル形式7zをRで解凍する

たまたまKaggleのDatasetsをのぞいていたのだが、そこで.7zという見慣れない形式のファイルが提供されていた。容量はテスト用のファイルで1GBを超えている。大きい…。ダウンロードするにも時間がかかるのだけど、それよりも問題だったのは、この.7z圧縮ファイルを解凍する方法がわからないことだった(勉強不足なのでこの.7zというのを扱ったことがなかった)。

Wikipediaによれば、「7z形式ではZIP形式に比べ高圧縮のファイルが作成できる」とある。なるほど、1GBを超えるような大規模ファイルはzipにするよりもこちらの方が都合が良さそうだ。

で問題は、解凍方法。せっかくなのでRでやりたい。検索したらすぐに答えが見つかった。

stackoverflow.com

archiveパッケージを使うと良いとのと。

github.com

archiveパッケージは、libarchiveの機能を利用して多くの圧縮ファイルを解凍する関数を提供する。また解凍だけでなく、圧縮ファイルの作成も行える。CRANには登録されていないのでGitHub経由でインストールする必要がある。なお事前にlibarchiveもインストールしておく。

library(archive)

圧縮ファイルを解答する関数はarchive_extract()。圧縮ファイルのパスと解答先のディレクトリを引数に指定して実行する。すると指定したディレクトリに解凍されたファイルが保存される。

archive_extract("~/Downloads/test.7z", "~/Downloads/")

圧縮ファイルにどのようなファイルが含まれているかは、対象のファイルを引数に取り、archive()を実行することで確認できる。こんな感じ。

archive::archive("~/Downloads/test.7z")
# # A tibble: 1,532 x 3
#             path    size                date
#            <chr>   <dbl>              <dttm>
#  1         test/       0 2017-03-31 10:21:10
#  2    test/1.jpg  648369 2017-03-31 10:20:08
#  3   test/10.jpg  595322 2017-03-31 10:20:09
# ...

めでたしめでたし。