★データ解析備忘録★

ゆる〜い技術メモ

Emacsで作るオレオレRStudio

この記事は RStudio Advent Calendar 2016Emacs Advent Calendar 2016 22 日目の記事です。

はじめに

みなさん RStudio 使ってますか? 僕は最近使用頻度がめっきり減ってしまいました。 なぜか?Emacsで事足りる場面がほとんどになってしまったからです。

実は、今回の記事の内容は 11/27 に行われた Japan.R という R の勉強会でイントロダクション的なことを喋りました。

github.com

今回の記事では、その補足等を書けたらなと思います。

そもそもなぜ RStudio じゃダメなのか

僕が RStudio に持ってる不満は以下です。

  • エディタとして貧弱
  • カラーテーマに好きな物がない
  • エディタが狭い
  • マウスをあんまり持ちたくない
  • R(と関連するいくつかの言語)しか書けない

エディタとして貧弱なのは言うまでもないでしょう。いくつかの有名なテキストエディタ(Vim, Atom, Sublime Text)と比べたら置換、カーソル移動、文字コード変換など様々な面で劣っています。 RStudio には Emacs, Vim キーバインドがあるにはありますが、そうじゃないんです。背景の色からカーソルの色・形まですべてを自分好みにしたいのです。テーマもafternoon-themeがいいんです!

また、僕は R 以外にもいろいろ書きます。論文執筆には TeX を使いますし、統計解析には SAS, Python も使いますし、スクレイピングRuby を使ったりもします。

無ければ作る!それが・・・

そんなふうに RStudio への不満がたまるなか、あることに気づきました。以下のツイートをご覧ください。

そう、... 無ければ作る!それがホクソエムの誓いだったはず!!!

ということで強力なエディタに自分で欲しい機能を追加して、オレオレ RStudio を作っていきます。 といっても、Emacs の場合は先人の方々がいろろなパッケージをすでに作ってくださってるので基本的には既存のものの組み合わせでいけます。 ただ、先日の RStudio v1.0 のリリースで R Notebooks が正式に実装されたりしてるので、そんな機能も追加していきます。

環境

作っていく前に僕の環境を示しておきます。

準備

まず、Emacs の基本的な操作(パッケージのインストールや Control, Meta, Super, Hyper キーの扱いなど)は既知とします。適宜おググりください。 僕は JIS 配列の Mac Book Pro と、家では US 配列の HHKB を繋げるという変態仕様なので、以下のように Key を設定しています。

;;キー設定----------------------------------------
;; Mac のときのキー設定
(when (eq system-type 'darwin)
  ;; 左 command を Meta キーにする
  (setq mac-command-modifier 'meta)
  ;; 右 command を Super キーにする
  (setq mac-right-command-modifier 'super)
  ;; 左 alt を alt キーにする
  (setq mac-option-modifier 'alt)
  ;; 右 alt を Hyper キーにする(英字配列のみ)
  (setq mac-right-option-modifier 'hyper)
  ;; fn キーを Hyper キーにする
  (setq ns-function-modifier 'hyper)
  )

また、Emacs のパッケージシステムである MELPA を使うために以下の設定を ~/.emacs.d/inite.el に書いておきます。

;;パッケージ管理----------------------------------
;; MELPA の設定
(require 'package)
(add-to-list 'package-archives '("melpa" . "http://melpa.milkbox.net/packages/"))
;; (add-to-list 'package-archives '("marmalade" . "http://marmalade-repo.org/packages/"))
(fset 'package-desc-vers 'package--ac-desc-version)
(package-initialize)

(add-to-list 'load-path "~/.emacs.d/elpa")

(require 'auto-install)
(setq auto-install-directory "~/.emacs.d/elpa/")    ; auto-install を elpa に変更
(auto-install-compatibility-setup)

以後、MELPA のパッケージは ~/.emacs.d/elpa" に自動で入り、また MELPA 以外のパッケージもここの置くこととします。

Emacs を RStudio っぽく

前置きが長くなりましたが、本題に入ります。

ESS

Emacs で R を使えるようにするためのパッケージです。MELPA から入ります。 実行のキーバインドなどは以下を参照。

www.okadajp.org

そして、.R ファイルを開くと自動で ESS の R-mode が起動して画面が上下に分割されるようにします。

.R ファイルを開くと、以下のようになります。

emacs_r1

諸事情により、ここの elisp コードは次の補完と一緒に示します。

補完

RStudio には優れた補完機能があります。これを実現するためには以下のパッケージを入れます。

auto-complete.el は MELPA から、auto-complete-acr.el は GitHub からインストールします。 参考記事はこちら

sheephead.homelinux.org

これで関数、オブジェクト名、変数名、ファイル名の補完が可能となります。*1 補完中のイメージは以下です。ポップアップの色などはもちろん好みに合わせて変更可能です。

ac

elisp コードを示します。まずは auto-complete のほう。この設定は R だけでなく他の言語を書くときも反映されます

;; ---------------------------------------------------------
;; auto-complete の設定
;; ---------------------------------------------------------
(require 'auto-complete)
(require 'auto-complete-config)
(global-auto-complete-mode t)
(setq ac-auto-show-menu 0.2) ; 補完リストが表示されるまでの時間
(define-key ac-completing-map (kbd "C-n") 'ac-next)      ; C-n で次候補選択
(define-key ac-completing-map (kbd "C-p") 'ac-previous)  ; C-p で前候補選択
;; ファイルパスの補完
(global-set-key [(alt tab)] 'ac-complete-filename)
(setq ac-dwim t)  ; 空気読んでほしい
;; 情報源として
;; * ac-source-filename
;; * ac-source-words-in-same-mode-buffers
;; を利用
(setq-default ac-sources '(ac-source-filename
                           ac-source-words-in-same-mode-buffers
                           ac-source-yasnippet))

(setq ac-auto-start 3);; 補完を開始する文字数

;; 色
(set-face-background 'ac-completion-face "#333333")
(set-face-foreground 'ac-candidate-face "black")
(set-face-background 'ac-selection-face "#666666")
(set-face-foreground 'popup-summary-face "black")  ;; 候補のサマリー部分
(set-face-background 'popup-tip-face "#ffffd8")  ;; ドキュメント部分
(set-face-foreground 'popup-tip-face "black")

次にさきほどの ESS の初期設定と合わせて R の設定をみてみます。

;; ---------------------------------------------------------
;; ESS の設定
;; ---------------------------------------------------------
(require 'ess-site)

;R 関連--------------------------------------------
;; パスの追加
(add-to-list 'load-path "~/.emacs.d/elpa")

;; 拡張子が r, R の場合に R-mode を起動
(add-to-list 'auto-mode-alist '("\\.[rR]$" . R-mode))

;; R-mode を起動する時に ess-site をロード
(autoload 'R-mode "ess-site" "Emacs Speaks Statistics mode" t)

;; R を起動する時に ess-site をロード
(autoload 'R "ess-site" "start R" t)


;; R-mode, iESS を起動する際に呼び出す初期化関数
(setq ess-loaded-p nil)
(defun ess-load-hook (&optional from-iess-p)
  ;; インデントの幅を 2 にする(デフォルト 2)
  (setq ess-indent-level 2)
  ;; インデントを調整
  (setq ess-arg-function-offset-new-line (list ess-indent-level))
  ;; comment-region のコメントアウトに # を使う(デフォルト##)
  (make-variable-buffer-local 'comment-add)
  (setq comment-add 0)

  ;; 最初に ESS を呼び出した時の処理
  (when (not ess-loaded-p)
    ;; 補完機能を有効にする
    (setq ess-use-auto-complete t)
    ;; helm を使いたいので IDO は邪魔
    (setq ess-use-ido nil)
    ;; キャレットがシンボル上にある場合にもエコーエリアにヘルプを表示する
    (setq ess-eldoc-show-on-symbol t)
    ;; 起動時にワーキングディレクトリを尋ねられないようにする
    (setq ess-ask-for-ess-directory nil)
    ;; # の数によってコメントのインデントの挙動が変わるのを無効にする
    (setq ess-fancy-comments nil)
    (setq ess-loaded-p t)
    (unless from-iess-p
      ;; ウィンドウが 1 つの状態で *.R を開いた場合はウィンドウを横に分割して R を表示する
      (when (one-window-p)
        (split-window-below)
        (let ((buf (current-buffer)))
          (ess-switch-to-ESS nil)
          (switch-to-buffer-other-window buf)))
      ;; R を起動する前だと auto-complete-mode が off になるので自前で on にする
      ;; cf. ess.el の ess-load-extras
      (when (and ess-use-auto-complete (require 'auto-complete nil t))
        (add-to-list 'ac-modes 'ess-mode)
        (mapcar (lambda (el) (add-to-list 'ac-trigger-commands el))
                '(ess-smart-comma smart-operator-comma skeleton-pair-insert-maybe))
        ;; auto-complete のソースを追加
        (setq ac-sources '(ac-source-acr
                           ac-source-R
                           ac-source-filename
                           ac-source-yasnippet)))))

  (if from-iess-p
      ;; R のプロセスが他になければウィンドウを分割する
      (if (> (length ess-process-name-list) 0)
          (when (one-window-p)
            (split-window-horizontally)
            (other-window 1)))
    ;; *.R と R のプロセスを結びつける
    ;; これをしておかないと補完などの便利な機能が使えない
    (ess-force-buffer-current "Process to load into: ")))

;; R-mode 起動直後の処理
(add-hook 'R-mode-hook 'ess-load-hook)

;; R 起動直前の処理
(defun ess-pre-run-hooks ()
  (ess-load-hook t))
(add-hook 'ess-pre-run-hook 'ess-pre-run-hooks)

;; auto-complete-acr
(require 'auto-complete-acr)

やや長いですが、ここまででも大部 RStudio っぽくなります。

オブジェクト構造の確認

RStudio の便利な機能にオブジェクトの str()summary() を表示してくれる機能があります。 デフォルトでは右上のペインに一覧表示されてるやつですね。

正直、普段は一覧されている必要はないです。ただ、オブジェクトの str() が簡単に見られるのは便利です。

そこで、ess-R-object-popup.elを MELPA から入れ、以下の設定を書きます。

;; C-c C-g で オブジェクトの内容を確認できるようにする
(require 'ess-R-object-popup)
(define-key ess-mode-map "\C-c\C-g" 'ess-R-object-popup)

こうすると、オブジェクトにカーソルを持っていって \C-c \C-g すると RStudio のあれがポップアップで表示されます。

popup

そうじゃなくて一覧で見たいんだって場合については後述します。

オブジェクトの中身

RStudio だと右上のオブジェクト名をクリックすると、エディタ画面にタブができて中身がそのまま見れますよね。 ess-R-data-view を MELAPA からインストールすると、オブジェクト名で \C-c V すると中身を別バッファで開きます。

data-view

このパッケージの詳細は以下。

sheephead.homelinux.org

ヘルプを引きたい

ここまでは既存のパッケージでしたが、ここからは自作関数も交えつつでいろいろ設定していきます。

関数のヘルプ、オブジェクトのヘルプを引きたい場面があると思います。これをhelmという Emacs における超強力な候補選択パッケージを使って実現します。 helm そのものについてはるびきちさんの記事が参考になります。

さて、helm でヘルプを引くパッケージとしては実は myuhe さんの helm-R.el というのがあるのですが、長らくメンテナンスされてない&候補が出て来るまでがめっちゃ遅いという不具合があったので、helm-myR.elとして作り直しました。

github.com

まだ README もないお粗末な状態ですが、このパッケージは 2 つの関数を提供します。 僕は以下のようにキーアサインしました。

;; helm インタフェースで 関数のヘルプをひく
(require 'helm-myR)
(define-key ess-mode-map (kbd "C-c h") 'helm-for-R)
(define-key inferior-ess-mode-map (kbd "C-c h") 'helm-for-R)

(define-key ess-mode-map (kbd "A-h") 'helm-R-install-packages)
(define-key inferior-ess-mode-map (kbd "A-h") 'helm-R-install-packages)

R ファイルを編集しているときに \C-c h するとローカルのオブジェクトとパッケージ名、関数名の候補が以下のように出てきます。

helm-R

関数名を選択して RET すると

rmecabc

こんな感じでヘルプが別バッファで開きます。 また、ライブラリ名を選択すると library() が実行されてパッケージが読み込まれます。

また、 \A-h すると R の install.package() で CRAN からインストールできるパッケージが候補として出てきてRETするとパッケージがインストールされます。

もっと RStudio っぽく

ここまででもかなり強力なのですが、もっと RStudio っぽくしたいときにはそうできようにしたいとおもいます。 そこで、Emacs におけるウィンドウ管理機能を応用します。使うパッケージは e2wm-R.elです。 また、後述する inlineR.elも必要です。 詳細は以下の記事が参考になります。

sheephead.homelinux.org

sheephead.homelinux.org

こんな感じになります。

screenshot_window
window_image

RStudio っぽい! このパッケージはオブジェクト一覧とプロット画面などを提供してくれます。 また、e2wm-R.elをを使うとオブジェクトや関数の一覧が見られます。 このパッケージは設定が多いのでお好みでいろいろ設定すればいいと思うのですが、僕は以下のように設定しました。

;; e2wm-R
(require 'e2wm-R)

;; e2wm:dp-R-view, e2wm:stop-management を toggleis する
(defun e2wm:toggle-management ()
  (interactive)
  (if (e2wm:pst-get-instance)
      (e2wm:stop-management) (e2wm:dp-R-view)))
(define-key ess-mode-map (kbd "C-,") 'e2wm:toggle-management)

これで、\C-, で RStudio っぽい画面と先程の広い画面を一発で切り替え可能になります。

R Markdown と R Notebooks

RStudio の便利な機能として、R の実行結果を HTML や PDF にコードのシンタックスハイライトつきで出力してくれるR Markdownがあります。 これを Emacs で実現するには、polymode.elを MELPA から入れます。これは仮想バッファを経由することで一つのバッファで複数の言語モードを扱えるようにするのですが、R と Markdown にも対応しています。 僕はyatemplate.elauto-insert.el を使って新規に.Rmd ファイルを作ったときに以下のテンプレートが挿入されるようにしています。(参考: 【新規ファイル作成支援】 auto-insert のテンプレートを yasnippet に進化させる)

---
title: "Untitled"
author: "y__mattu"
date: ""
output: html_document
---

```{r set, eval = TRUE, echo = FALSE, warning = FALSE, message = FALSE, comment = ""}
knitr::opts_chunk$set(echo = TRUE,
                      eval = TRUE,
                      warning = FALSE,
                      message = FALSE,
                      comment = "",
                      fig.height = 7,
                      fig.width = 7,
                      out.height = 400,
                      out.width = 400)


```
rmd1

このように、ちゃんと R と Markdown が別々にハイライトされます。

基本設定

R Markdown の HTML 化の正体

RStudio ではエディタ画面の knit ボタンを押すと HTML などに自動で変換してくれてプレビューまでできるのですが、これは実はコマンドでもできます。 というより、コマンドでの操作を RStudio 上でクリックで動くようにしているにすぎません。 コマンドでは、HTML 化を rmarkdown::render(.Rmd のファイル名) で行っています。コマンドでできることは Emacs でもできるので、これを実行する(Emacs の)関数を定義して、キーバインドを割当てます。*2

;; R Markdown-------------------------------------
(require 'poly-markdown)

;; R modes
(require 'poly-R)
(add-to-list 'auto-mode-alist '("\\.Rmd" . poly-markdown+r-mode))

;; html へ変換
(defun rmarkdown-to-html ()
  (interactive)
  "Run rmarkdown::rnder on the current file"
  "https://gist.github.com/kohske/9128031"
  (shell-command
   (format "Rscript -e \"library(revealjs); rmarkdown::render ('%s')\""
           (shell-quote-argument (buffer-file-name)))))

(define-key poly-markdown+r-mode-map "\C-c\C-h" 'rmarkdown-to-html)


;; html スライドに変換
(defun rmarkdown-to-slide ()
  (interactive)
  "Run slidify::slidify on the current file"
  (shell-command
   (format "Rscript -e \"slidify::slidify ('%s')\""
           (shell-quote-argument (buffer-file-name)))))

(define-key poly-markdown+r-mode-map "\C-c\C-l" 'rmarkdown-to-slide)

こうすることで、.Rmd を開いた状態で\C-c\C-hすると HTML にしてくれます。また、同様にして\C-c\C-lで{slidify}パッケージでの HTML スライド化を実現します。(ブラウザでのプレビューも自動化できそうですが、面倒なのでまだやってません。)

チャンク挿入

R Markdown でコードを挿入するときはチャンクを挿入してそのなかにコードを書くわけですが、ここもショートカットしちゃいましょう。もちろん RStudio にもソートカットが存在しますが。

;; チャンク挿入
(defun tws-insert-r-chunk (header)
  "Insert an r-chunk in markdown mode.
Necessary due to interactions between polymode and yas snippet"
  (interactive "sHeader: ")
  (insert (concat "```{r " header "}\n\n```"))
  (forward-line -1))

(define-key poly-markdown+r-mode-map (kbd "M-n M-r") 'tws-insert-r-chunk)

(defun tws-insert-py-chunk (header)
  "Insert a python-chunk in markdown mode.
Necessary due to interactions between polymode and yas snippet"
  (interactive "sHeader: ")
  (insert (concat "```{python " header "}\n\n```"))
  (forward-line -1))

(define-key poly-markdown+r-mode-map (kbd "M-n M-p") 'tws-insert-py-chunk)

(defun tws-insert-norm-chunk (header)
  "Insert a chunk in markdown mode.
Necessary due to interactions between polymode and yas snippet"
  (interactive "sHeader: ")
  (insert (concat "```{" header "}\n\n```"))
  (forward-line -1))

(define-key poly-markdown+r-mode-map (kbd "M-n M-c") 'tws-insert-norm-chunk)

ここでは R, Python, 普通のチャンクの 3 種類をわけてみました。例えば R であれば、M-n M-r を押してミニバッファにオプションを入力してRETすると、いい感じのチャンクが挿入されます。

R Notebooks について

RStudio v1.0 から正式に搭載された R Notebooks です。概要は @kazutan さんのスライドが分かりやすいです。

R Notebooks の機能としては

  • プロットや解析結果をインラインで表示
  • .nb.html ファイルの作成

の2つが主な機能かと思います。

まずインライン表示ですが、summary() などの結果についてはさきほどの ess-R-object-popup で機能的には十分です。要は HTML にしなくてもその場で見られればいいので。

また、.nb.html の作成については、実は R MarkdownYAML 部分を少し変えるだけです。具体的には、output: html_document の部分を html_notebookに変えます。あとは普通に knit するだけです

ということで、画像のインライン表示だけなんとかすればいいことになります。

画像のインライン表示

もともと Emacs には画像を表示する機能があるのですが、それを R のコード上でやるのが inlineR.el です。 使い方は作者の解説記事が詳しいです。

sheephead.homelinux.org

以下のような状態を目指します。

rmd

設定です。plot 系や ggplot 系全部に対応しています。

;; ソースコードにインライン画像を埋め込む
(require 'inlineR)
(define-key ess-mode-map "\C-ci" 'inlineR-insert-tag)
(setq inlineR-re-funcname ".*plot.*\\|.*gg.*") ;; 作図関数追加

;; インライン画像表示の切り替え
(defun uimage-mode-toggle ()
  (interactive)
  (uimage-mode 'toggle))

(global-set-key (kbd "A-i") 'uimage-mode-toggle)

その他

オブジェクトに色付け

これは完全にお好みですが、<-で代入されるオブジェクトにシンタックスハイライトを適用することができます。

;; オブジェクトにも色付け
(defvar ess-R-fl-keyword:assign-vars
  (cons "\\(\\(?2:\\s\"\\).+\\2\\|\\sw+\\)\\s-*\\(<-\\)"
        '(1 font-lock-variable-name-face)))

(add-to-list 'ess-R-font-lock-keywords '(ess-R-fl-keyword:assign-vars . t) t)

パイプ演算子の挿入

{magrittr}パッケージで提供されるパイプ演算子を入力して改行、インデントまでを一気にやってしまいます。

;; パイプ演算子
(defun R_operator ()
  "R - %>% operator or 'then' pipe operator"
  (interactive)
  (just-one-space 1)
  (insert "%>%")
  (reindent-then-newline-and-indent))
(define-key ess-mode-map (kbd "s-5") 'R_operator)
(define-key inferior-ess-mode-map (kbd "s-5") 'R_operator)

でもやっぱり RStudio が恋しい!

そんなときは Emacs で現在開いているファイルを既定のプログラムで開く関数を定義します。 こうすれば.R や.Rmd ファイルのデフォルトのプログラムを RStudio にするだけで自由に Emacs と RStudio を行き来できます。 Emacs で auto-revert-mode を on にしておくと、2つ同時に同じファイルを開いていてもどちらかで編集して保存すればもう片方に変更が反映されます。

;; 現在のファイルを規定のプログラムで開く
(defun open ()
  "Let's open file!!"
  (interactive)
  (cond ((eq system-type 'darwin)
         (shell-command (concat "open " (buffer-file-name))))
        ((eq system-type 'windows-nt)
         (shell-command (concat "cygstart " (buffer-file-name))))
        )
  )
(global-set-key "\C-c o" 'open)

;; 設定しておくとよい
(global-auto-revert-mode t)

RStudio とは直接関係ないやつ

Evil モード

Emacs 上で Vim キーバインドをエミュレートするやつです。小指の保護になったり、gd でオブジェクトや関数の定義箇所に飛べたりします。

electric-operator.el

=, + などの各種演算子の前後に自動でスペースを入れてくれます。

(require 'electric-operator)
(add-hook 'ess-mode-hook 'electric-operator-mode)

;; ESS では%を演算子から外す
(electric-operator-add-rules-for-mode 'ess-mode
  (cons "%" nil))

rainbow-deliminators.el

カッコが重なったときに色分けしてくれます。カッコ多めの R では見やすくなるんじゃないかと思います。

magit.el

EmacsからGUIっぽくgitを操作できるやつ

まとめ

... 無ければ作る!それがホクソエムの誓いだったはず!!!

Enjoy!!

*1:パッケージ名の補完は鋭意作成中です。

*2:ただ、僕の環境だけなのか分かりませんが、ディレクトリやファイル名に日本語が入ってるとうまくいかないようです。