cucumber flesh

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

hereパッケージの導入でファイル参照のパス問題の悩みを解消

去年から気になっていたものの、その利点や使い道について理解できていなかったhereパッケージ、ようやくにして少しわかった気がする。今は声を大にして言える。hereは良いぞ。hereをプロジェクトに導入することで、これまでにあったWindows - macOS間でのパス表記の違いや作業ディレクトリの階層性によるパスが正しく指定できないといった課題を解決することができるかもしれない。と言う訳で布教用の記事を書く。また、テスト用のリポジトリも用意しているので、hereを使ってそのご利益を享受されたい方はこちらをcloneなりダウンロードするなりして手前で実行してほしい。

github.com

参考) https://github.com/jennybc/here_here

問題

Rを使っていて、ファイルの保存先を指定するのに失敗してイラつきを覚える、そんな経験は誰しもがあるだろう。Rでは、作業ディレクトリ (working directory。getwd()で現在の作業ディレクトリを確認する)と呼ばれる基盤となるディレクトリが設定される。これにより目的のファイルまで簡単にたどり着くことができるなどの利点もあるのだが、複数のプロジェクトを運用している場合、プロジェクトのフォルダをまたいでしまったり、作業ディレクトリを頻繁に切り替える作業が発生してしまう(setwd()により作業ディレクトリは変更される)。皆さんには作図したファイルの保存先にfigフォルダを作っていたのに、全く異なる場所にファイルを保存してしまった、ということがないだろうか?私はしばしばそんな経験をした。ここに書いたにもパスに関する多様な問題があるだろうが、もっとも頻繁に遭遇する問題だと思われる2点について、次にまとめる。

RStudioの一機能であるRプロジェクトは、プロジェクトに応じた作業ディレクトリの構築を可能にする。Rプロジェクトであることを示す .Rprojファイルはプロジェクトとして扱うフォルダのトップの階層に置かれ、そこを起点とした作業ディレクトリでプロジェクトを回していくことができる。プロジェクトに関する全てのファイルをプロジェクトの中に置くことで利便性が増し、上記の問題を防ぐことが可能となる。

一方で、ファイルの参照形式はOS間で異なることがあり、参照がうまくできないことがしばしばある。次のコードはWindowsにおいてdataフォルダ中のiris.csvを読み込むための処理だが、これをUNIXベースのOSで実行するとエラーとなる。UNIXではフォルダの区切り文字に/を使うのが習慣であり、他方Windowsでは区切り文字に/の他、\\ ()の使用が認められるためである。

# Windowsで実行可能な読み込みはUNIX環境ではエラーとなる
read.csv("data\\iris.csv")

またプロジェクトの外部で、ドライブからパスを指定しなくてはいけない時も、Windowsでは次のように指定する。

# ドライブからファイルを参照する
read.csv("C:/documents/uribo/here_demonstration/data/iris.csv")

UNIXではユーザディレクトリは /home/<username>であったり、/Users/<username>となっていたりするがホームディレクトリは ~で表記を省略することもできる。また省略されたパスをpath.expand()により完全な形で示せる。

# ホームディレクトリからファイルを参照する
read.csv("~/here_demonstration/data/iris.csv")
path.expand("~/here_demonstration/data/iris.csv")
## [1] "/home/rstudio/here_demonstration/data/iris.csv"

これはプロジェクト機能をもってしても残り続ける課題である。

またRmdファイルをプロジェクト内のフォルダに保存しておくと、プロジェクトの作業ディレクトリよりも深い階層でRコードを実行することになるため、コンソールでの実行とRmdファイル中のコードの記述が異ってしまう。具体的には、data/iris.csvを参照するには、一つ階層を遡って../data/iris.csvとしなくてはいけない。また、Rmdファイルをより深い階層においた場合、さらに遡って../../data/iris.csvと記述することとなる。これに対応するのは手間である。

hereによるファイル参照

こうした課題を解決するために、hereパッケージは優れた機能を提供する。hereはCRANNからインストールができる。この記事を書いた際のバージョンは0.1である。Windows環境にインストールした際、backportsパッケージの古いバージョンをインストールしておかないといけないとエラーになったので、参考のリンクを貼っておく。

library(here)
## here() starts at /home/rstudio/here_demonstration

hereパッケージを読み込むと、上記のメッセージが出力される。出力先のパスが基点を示している。Rプロジェクトが同じ階層内にある場合、その階層が基点にみなされる。

hereの考え方としては、あるディレクトリをプロジェクトとみなした場合、ファイルの参照を常にプロジェクトの基点から表記する、というものである。関数hereはパスを表現するのに用いられるが、従来の区切り文字で表記する方法の他にRの文字列としてフォルダ名を与えていくことでパスを表現することもができる。

# 従来のパス表記方法
here("data/iris.csv")
## [1] "/home/rstudio/here_demonstration/data/iris.csv"
# 文字区切りでフォルダ、ファイルを示す
here("data", "iris.csv")
## [1] "/home/rstudio/here_demonstration/data/iris.csv"

パスを出力するだけなので、ファイルの有無は気にしない。

file.exists("aaa/bbb/ccc.csv")
## [1] FALSE
here("aaa", "bbb", "ccc.csv")
## [1] "/home/rstudio/here_demonstration/aaa/bbb/ccc.csv"

c()を使ってこんなこともできる。複数の文字列を指定した際は各要素の位置に対応したパスが出力される。

here("data", c("aa", "bb"), "iris.csv")
## [1] "/home/rstudio/here_demonstration/data/aa/iris.csv"
## [2] "/home/rstudio/here_demonstration/data/bb/iris.csv"
here("data", c("aa", "bb"), c("iris.csv", "mtcars.csv"))
## [1] "/home/rstudio/here_demonstration/data/aa/iris.csv"  
## [2] "/home/rstudio/here_demonstration/data/bb/mtcars.csv"

here()がその効力を発揮するのは、特に深い階層にあるRコードやRmdファイルを実行する時である。サンプルリポジトリに作成したhoge/fuga/sample2.Rmdからdata/iris.csvを参照するには../../data/iris.csvと記述しなくてはいけなかったところをhere("data", "iris.csv")の記述に置き換えられる。data/iris.csvを参照するには、常にhere("data", "iris.csv")とすれば良いのである。

dim(read.csv(here("data/iris.csv")))
## [1] 150   6
# 作業ディレクトリを変更しても data/iris.csvの参照方法は変わらない
setwd("hoge/fuga/")
# The working directory was changed to /home/rstudio/here_demonstration/hoge/fuga inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
dim(read.csv(here("data/iris.csv")))
## [1] 150   6
# 作業ディレクトリの変更は維持される
getwd()
## [1] "/home/rstudio/here_demonstration/hoge/fuga"

なお、RMarkdownのチャンクでは、チャンク内で完結し、チャンクが切り替わると基点に戻る。

getwd()
## [1] "/home/rstudio/here_demonstration"

素晴らしい!

Rプロジェクトの外部でhere!

hereはRプロジェクトのあるフォルダを基点とするのが基本だが、他にも基点となり得るファイルやフォルダが認められている。GitリポジトリやRパッケージ開発に必要なDESCRIPTIONファイル(^Package:で始まること)などである。加えて、独自の.hereファイルがあるフォルダも基点にすることができる。これはRプロジェクトを用いない分析プロジェクトでは有効だろう。任意の作業ディレクトリでset_here()を実行あるいはpath引数にパスを指定して、.hereを作成できる。

パス問題は、これまで多く悩まされてきたものなので、Rプロジェクトの台頭によって大分ストレスがなくなったが、hereを導入することでさらにストレスフリーで作業を進められそうである。

Enjoy!

Rから離れたくない人向けのDocker環境の操作: RStudio Serverを分析・開発の基盤にするために

この記事はRStudioアドベントカレンダーの21日目の記事です。もうすぐこのアドベントカレンダーも終わりですね。ハヤイ!

今年のはじめにこんな記事を書きました。

uribo.hatenablog.com

皆さんはDockerを利用していますでしょうか。今年のデータ分析系のアドベントカレンダーでもぞうさんがdockerが取り上げられています。

qiita.com

Rユーザの自分にとっては、Dirk EddelbuettelCarl Boettigerなどが携わるrockerプロジェクトが整備されているのが嬉しいです。

notchained.hatenablog.com

rockerプロジェクトのdockerイメージの多くはRStudio Serverをイメージのベースとしており、お手軽にローカル環境とは別のRStudio環境が構築できます。また必要に応じて、rockerのdockerimageをベースに俺俺dockerimageへ拡張するのも簡単です。俺俺dockerimageの必要性についてはTokyo.RのLTで発表を行った過去の資料もあります。

speakerdeck.com

そんな便利なdockerですが、dockerコマンドを覚えられなかったりコマンドラインから実行するのが面倒だったりします。特に自分は常にRStudioの画面を開いていたい側の人間なのでRからdockerコンテナやイメージの操作ができると良いです。良いです(大切なことなので二回言いました)。

... というわけでdockerパッケージを使います。これにより、Rから直接dockerのあれこれが行えるようになります(実態はPythondockerモジュールreticulateパッケージでラップしているだけ)。大好きなRからできるので、dockerで何ができるかも理解しやすくて良いですね。

github.com

dockerパッケージ

以下の説明はdockerが利用できる環境で、dockerモジュールがインストールされていることを想定しています。あ、Rのdockerパッケージのインストールも忘れずに。CRANからインストールできます。まずはdockerを起動した状態で次のコードを実行します。Rスクリプトと一緒にコマンドラインで実行する際のdockerコマンドも示します。

# install.packages("docker")
library(magrittr)
library(docker)
client <- docker$from_env()

インストールされているイメージ一覧を出力します。

# docker images
client$images$list()
# [[1]]
# <Image: 'uribo/tiny_rocker_geospatial:latest'>
# 
# [[2]]
# <Image: 'rocker/tidyverse:latest'>
# 
# [[3]]
# <Image: 'kaggle/rstats:latest'>
# 
# [[4]]
# <Image: 'rocker/rstudio:latest'>
# 
# [[5]]
# <Image: 'rocker/r-base:latest'>

Dockerイメージをpullしてくるにはpull関数を使います。

# docker pull uribo/tiny_rocker_geospatial
client$images$pull("uribo/tiny_rocker_geospatial")

利用するdockerイメージの用意ができたらコンテナを起動しましょう。次のRスクリプトの実行は、docker run --rm -p 8787:8787 uribo/tiny_rocker_geospatial と同じです。

rss_instance <- client$containers$run(image = "uribo/tiny_rocker_minimum",
                                      remove = TRUE,
                                      # RStudio上で操作を続ける際はTRUEにする
                                      detach = TRUE,
                                      ports = list("8787/tcp" = "8787"))
rss_instance$start()

これでコンテナが起動しました。localhost:8787をブラウザで開きましょう。また次のコードでコンテナの一覧を確認してみると、きちんとコンテナが動いていることがわかります。

# docker ps -a
client$containers$list()
# [[1]]
# <Container: a94576e6a2>

コンテナを終了するにはstop関数を使います。現在のコンテナはremoveオプションを有効にしていたため、コンテナを停止すると自動的にコンテナが削除されます。

# docker stop <コンテナID>
rss_instance$stop()

# 停止とともにコンテナは削除される
# docker ps -a
client$containers$list()
# list

基本はこれでOKです。既存のコンテナを起動するには、まずコンテナのIDを知る必要があります。あるいはコンテナに名前をつけている場合にはそれを使うことも可能です。次はコンテナに名前をつけ、停止しても再起動できるように永続化させましょう。

rss_instance <- client$containers$run(image = "uribo/tiny_rocker_geospatial",
                                      remove = FALSE,
                                      detach = TRUE,
                                      name = "dev_1712",
                                      ports = list("8787/tcp" = "8787"))
# 再起動を行うため停止します
rss_instance$stop()

では停止しているコンテナを再起動します。再起動の方法には色々ありますが、ここではコンテナ名からコンテナidを抽出し、それを利用する方法を用います。

httr::http_error("http://localhost:8787")
# Error in curl::curl_fetch_memory(url, handle = handle) : 
#   Failed to connect to localhost port 8787: Connection refused
container_id <- client$containers$list(all = TRUE, filters = list("name" = "dev_1712")) %>% 
  magrittr::extract2(1) %>% 
  magrittr::use_series(id)

cll <- docker$APIClient()
cll$start(resource_id = container_id)
httr::http_error("http://localhost:8787")
# [1] FALSE

APIClient関数からcllというオブジェクトを作成し、コンテナを起動しました。dockerパッケージは便利ですが、コンテナの再起動をできないとRStudioを終了できないので困りますが、これで安心です。

明示的にコンテナを削除するには次のようにします。

cll$stop(resource_id = container_id)
# docker rm <コンテナ ID>
cll$remove_container(resource_id = container_id)

前述の通りdockerパッケージはPythonのdockerモジュールをラップしているので、dockerパッケージで何ができるのかを知りたい時はドキュメントを読むのが早いです。

ローカル環境のRStudioの設定を適用する

ここからは応用編です。起動したRStudio Serverでは、当然ながらRStudioの初期設定が適用されています。これではコンテナを作る度にぽちぽちとマウス操作によって設定をしていく必要が生じるので、楽にやりたいです。

なので、ローカルの設定をRStudio Serverへコピーするという方法をとっています。

system(
  paste0('docker cp /Users/uri/.rstudio-desktop/monitored/user-settings/user-settings ',
        container_id,
        ':/home/rstudio/.rstudio/monitored/user-settings/'))

f:id:u_ribo:20171221211612g:plain

APIがあるかと思ったのですが、なかなかうまくいかず...まだ試行錯誤なのですがこの方法で一応ローカルで動かしているRStudioのテーマやパネルの配置等の設定をRStudio Serverへ持ってくることができます。

注意としては、一度対象のコンテナを起動していないとコピーが成功しない(.rstudioという隠しフォルダが作成されない)、パスは絶対パスでなければいけないの2点です。

マウント: ローカルのファイルと同期させる

docker環境で作業して、それをローカルに保存したり、ローカルのコードをdocker環境で試す、ということがあります。それにはvolumeオプションを利用します。これはコンテナの作成時にやっておきます。

まずはdockerコンテナに構築したいローカルのパスとdockerコンテナ内の関係をオブジェクトとして作成します。modeは書き込みと保存ができるようにrwとしましょう。

mount <- list("/Users/uri/Documents/projects2016/jpmesh" = 
            list("bind" = "/home/rstudio/jpmesh",
                 # rw: read and write (ro: read only)
                 "mode" = "rw"))

rss_instance <- client$containers$run(image = "uribo/tiny_rocker_geospatial",
                                      remove = TRUE,
                                      detach = TRUE, 
                                      volumes = mount,
                                      name = "dev_1712",
                                      ports = list("8787/tcp" = "8787"))

f:id:u_ribo:20171221211810p:plain

ユーザ、パスワードを変更する

RStudio Serverでは初期ユーザ名とパスワードがrstudioになっていますが、運用していくためには変更したいです。これもコンテナの起動時に設定しておきます。ここではenvironmentオプションを使います。USERPASSWORDという値をそれぞれ任意のユーザ、パスワードにすることが可能です。以下は、ユーザ、パスワードの変更とついでに管理者ユーザとなるオプションも有効にする例です(追加でシステムのインストールが必要となることがあるため)。

rss_instance <- client$containers$run(image = "uribo/tiny_rocker_geospatial",
                                      remove = TRUE,
                                      detach = TRUE,
                                      environment = list("USER" = "piyo",
                                                         "PASSWORD" = "hogepass", 
                                                         # sudo が有効になる
                                                         "ROOT" = "TRUE"),
                                      ports = list("8787/tcp" = "8787"))

f:id:u_ribo:20171221211501g:plain

俺俺dockerfileの作成方法についてはまたの機会に。ちなみに、この記事で使っているコンテナは俺俺イメージの一つです。地理空間データを扱う上で欠かせない存在となったsfパッケージのインストールが面倒なのでサクッとできるようにしています。

https://hub.docker.com/r/uribo/tiny_rocker_geospatial/

Enjoy 🐳

2017年度版 RStudioを使ったReproducible Research、補足ポエム

この記事はRStudioアドベントカレンダーの10日目の投稿の補足です。私ももう、ゴールしても良いよね、という気になってきました()。本体は以前書いた記事で申し訳ないのですが、

qiita.com

になります。古くなったので刷新し、追記をしました。

RによるReproducible Researchの話は@psycle44さんによる5日目の記事でも書かれているのですが、補足として私の所感を述べたいと思います。以下はざっくりとした私の見解です。ポエムです。

ここで扱う、Reproducible Researchの話はRやPythonで記述されたコードにおける再現性の話です。海外では数年前から流れができ始め、現在では市民権を得て大きな流れになっているように感じます。一方日本国内でも少しずつですが認知されつつあり、一部のユーザの間ではかなり浸透しているという印象があります。

そもそも再現性とは

学術論文では、調査地や調査対象、時期に始まり、データを得るための詳細や解析に利用したツール、ライブラリとそのバージョンまで書いたりしますよね。良い論文というのはしっかりとした再現性が担保できることだという話も聞いたことがあります。

で、現在ではRやPythonといったスクリプト言語で統計解析やモデリングを実行しますよね。そうなったら、そのコードで書いた内容も「方法」として扱っても良いよね、となるわけです。そうした意味ではバージョン管理をすることは一つの選択肢となり得ると思います。

Git?

Gitはバージョン管理システムの一つで、プログラマやエンジニアの中で使われてきたものですが最近ではデザイナや書籍の編集者も使っています。

Gitの恩恵はいろいろあり、その感じ方も多様ですが、Reproducible Researchでの再現性を保証するのに十分な機能を備えている気がします。Gitを用いる理由は自分自身のためでもあり、共同編集者のためでもあり、第三者のためでもあります。

以前、研究者に対してバージョン管理システムの話をしたことがありましたが、「Dropboxのように意識せずとも使えるようなものでないと使う気にならない」と言われました。Gitは確かにそこまで気がきくものではありません。ですが得られるものは大きいです。ぜひ一度使ってみてほしいものです。またGit以外で他に良いツールがあれば教えてほしいです。

何も全てのGitコマンドに精通する必要はありません。コミット、プッシュ、そしてブランチという概念について少しでもわかっていれば十分にGitを導入することができると思います。私も必要な操作については都度調べたりします。そしてRStudioではこうした最低限必要であろう機能が備わっています。逆に複雑なGitの機能は実装されていません(Shell機能を通して実行することはできます)。

まだRStudio or Gitを使ったことがないという人は今日できることから始めてみてはどうでしょう。まずはコミットで記録していくだけで良いと思います。

Happy Coding with R!

過去の資料とか

speakerdeck.com

speakerdeck.com

オススメ

speakerdeck.com