memiscパッケージのrecodeのバグ

追記(2011/11/07):memisc(0.95-35)で修正されました。

memisc(0.95-33)のrecode関数にはバグがある模様。

> library(memisc)
> x <- 1:100
> y <- recode(x, "low" <- range(min, 30), "hi" <- range(31, max))
> x[1:10*3] <- NA
> z <- recode(x, "low" <- range(min, 30), "hi" <- range(31, max))
 以下にエラー if (any(nevtrue)) { :  TRUE/FALSE が必要なところが欠損値です 

NAが含まれる数値ベクトルを渡すとエラーが出る。

以下のように,NAに一時的に数値を割り当てて対処するのがたぶん一番楽。

> x[is.na(x)] <- 999
> z <- recode(x, "low" <- range(min, 30), "hi" <- range(31, 100),
+             otherwise=NA)
> z
  [1] low  low  <NA> low  low  <NA> low  low  <NA> low  low  <NA> low  low 
 [15] <NA> low  low  <NA> low  low  <NA> low  low  <NA> low  low  <NA> low 
 [29] low  <NA> hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi  
 [43] hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi  
 [57] hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi  
 [71] hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi  
 [85] hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi   hi  
 [99] hi   hi  
Levels: low hi

バク報告したいけど英語メールがめんどくさい…。
でも,memisc推進の妨げになるので近いうちにします。

コマンドのみペースト

Windows限定?

> x <- 1 + 2
> x
[1] 3
> y <- x * 10
> y
[1] 30
> x +
+ y +
+ 10
[1] 43

こんなふうに,ブログとかPDFとかに実行結果が貼り付けてあって,元のスクリプトがない場合がありますよね。
いままではエディタに貼り付けてからちまちまと頭の記号とか出力とかを消してたんですが。

f:id:phosphor_m:20110628180952p:image

コンソールを右クリックすると,「コマンドのみペースト」というのがあります。
存在は知っていたんですが,使い方がよくわかっていませんでした。

そう,つまり,これは「出力のコピーからコマンドのみをペースト」する機能でした。
スクリプトが必要ならば,いったんペーストしてからhistory()を実行すればいいわけです。
いままで気付かなかったことにショックを受けたのですが,みなさんは知ってましたか?

Rookパッケージがすごい!Rだけで1分で0からウェブアプリ

要R (≥ 2.13.0)です。では,おもむろに以下のスクリプトを実行してください。

install.packages("Rook")
s <- Rhttpd$new()
s$start(quiet=TRUE)
s$browse(1)

ブラウザが立ち上がり,テスト用のウェブアプリが表示されました。たったこれだけで!

なぜこんなことができるかというと,Rは実はウェブサーバーを内蔵していて,Rookパッケージはこれを使ってウェブアプリを稼働させています

Hello, World!

ではお約束にとりかかりましょう。

app.hw <- function(env){
    res <- Rook::Response$new()
    res$write("<html>\n<head><title>Test</title></head>\n<body>\n")
    res$write("<h1>Hello, World!</h1>\n")
    res$write("</body>\n</html>\n")
    res$finish()
}
s$add(app=app.hw, name="HelloWorld")
s$browse("HelloWorld")

これだけです!ほとんど説明は要らないぐらい簡単ですが,まず,最初のスクリプトで生成したsというオブジェクトは,Rhttpdオブジェクトで,ウェブアプリとサーバーの橋渡しをします。Rhttpdオブジェクトに関数の形で作ったウェブアプリを渡します。RhttpdオブジェクトはR5クラスなので,addというメソッドを使ってs$add(app=ウェブアプリの関数, name=ウェブアプリの名前)という形でウェブアプリを登録します。browseメソッドは単なるユーティリティ関数で,ウェブアプリ名かウェブアプリのインデックスを与えるとブラウザを起動してそのウェブアプリを表示してくれます。

ウェブアプリとなる関数の中身ですが,これも単純ですね。まず,今回は使っていませんが,引数はenvだけです。次に,Rookパッケージの名前空間にあるResponseオブジェクトにアクセスしています。ResponseオブジェクトはResponseクラスのgenerator objectで,newメソッドを使ってResponseオブジェクトを生成しています。このあたりはR5クラスの話になるので,無視してもらってもかまいません。あとは,Responseオブジェクト(res)にwriteメソッドでhtmlを与えてやるだけです。最後にfinishメソッドで終了します。

Rhttpdオブジェクトに登録されているウェブアプリは,printメソッドで一覧できます。

> s$print()
Server started on 127.0.0.1:21483
[1] RookTest   http://127.0.0.1:21483/custom/RookTest
[2] HelloWorld http://127.0.0.1:21483/custom/HelloWorld

Call browse() with an index number or name to run an application.

browseメソッドに渡すアプリ名やインデックスはここで確認できます。表示されるURLに直接アクセスしてもかまいません。

Rookの本質

超簡単ウェブアプリサーバーというのは実は仮の姿で,RookはRubyのRackのRでの実装というのが本質です。Rackがどのようなものかはこの記事が分かりやすいですが,要するにサーバーとアプリの橋渡しをするミドルウェアで,Rackに対応したアプリを作れば,Rackに対応したサーバーすべてに対応したことになる,というものです。とはいえRが使えるサーバーは現状Apache(rapache)しかないうえに,rapacheはまだRookに対応していないので,現状では実質的に超簡単ウェブアプリサーバーでしかないのですが。ちなみにRookの作者はrapache,brewの作者と同一なので,rapacheのRook対応は近いうちに実装されると思います。

もう少しウェブアプリっぽいもの

次はリクエストを受け取って処理をするアプリを作ります。といってもRook付属のサンプルなのですが。サンプルはライブラリのRookフォルダの中のexampleAppsフォルダに入っています。

app.hw2 <- function(env){
    req <- Rook::Request$new(env)
    res <- Rook::Response$new()
    friend <- 'World'
    if (!is.null(req$GET()[['friend']]))
	friend <- req$GET()[['friend']]
    res$write(paste('<h1>Hello',friend,'</h1>\n'))
    res$write('What is your name?\n')
    res$write('<form method="GET">\n')
    res$write('<input type="text" name="friend">\n')
    res$write('<input type="submit" name="Submit">\n</form>\n<br>')
    res$finish()
}
s$add(app=app.hw2, name="HelloWorld2")
s$browse("HelloWorld2")

今度はenvを使っています。Rook::Request$new(env)でRequestオブジェクトを生成しています。RequestオブジェクトからはPOSTメソッドやGETメソッドでリクエストの内容を取り出せます。他にも多くの情報が取り出せます。詳細はhelp(Request)にあります。アプリの中身はこれまた単純で,GETの中身がNULLならば何もせず,NULLでなければfriendをGETの中身で上書きする,というものです*1

ところで,formが出てきたのでXSS脆弱性に気をつけましょう。RookにはUtilsクラスがあり,便利なクラスメソッドが用意されています。そのなかにescapse_htmlというのがあります*2

> Utils$escape_html("<>'\"&")
[1] "&lt;&gt;&#39;&quot;&amp;"

もう少しRっぽいもの

今度はファイルを直接読み込んで登録します。読み込むファイルの仕様の記述は見つけられなかったのですが,ファイル内のappという名前の関数を読み込むようです。

s$add(app=system.file("exampleApps", "summary.R", package="Rook"),
      name="Summary")
s$browse("Summary")

CSVファイルを読み込んでsummary()に通した結果を出力するアプリです。

brew

brewパッケージはいわゆるテンプレートエンジンで,htmlの中にRのスクリプトを埋め込んだりするためのものです。詳しくはitoshiさんの連載で。参考にさせていただきました,ありがとうございます。

brewはRookといっしょにインストールされているはずです。

こちらの記事にならってヒストグラムを表示するアプリを作ってみます。では,次のコードをindex.rという名前で保存してください。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <head>
    <title>Histogram</title>
  </head>
<body>
  <% brew('./hist.r') %>
</body>
</html>

さらに,次のコードをhist.rという名前で保存してください。

<%
image_dir <- file.path(tempdir(), "plots")
if(!file.exists(image_dir)) dir.create(image_dir)

filepath  <- tempfile("hist_", tmpdir=image_dir, fileext=".png")
filename  <- basename(filepath)

data <- rnorm(1000)
png(filepath)
hist(data)
dev.off()
%>
<img src="./plots/<%=filename%>" />

まず,index.rはほぼhist.rの中身を読み込むだけのものです。hist.rは正規分布にしたがうデータからヒストグラムを作成し,imgタグを返しています。Rのスクリプトは<% 〜 %>の中か<%= 〜 %>の中に書きます。<%= 〜 %>の場合はスクリプトの出力がhtml内に埋め込まれます。

簡単なスクリプトですが,一カ所だけ変なところがあります。いま,filepathの中身はtempdir()を使っているので"C:\\Users\\masahiro\\AppData\\Local\\Temp\\RtmpUzjlma/plots\\hist_9654f.png"のようになっています。にもかかわらず,imgのsrcは"./plots/<%=filename%>"です。これはhtmlに出力されるさいには"./plots/hist_9654f.png"のようになります。このあたりのカラクリは次のスクリプトで。

brew.app <- Builder$new(Static$new(urls="/plots",root=tempdir()),
                        Brewery$new(url="/",root="(index.rとhist.rが置いてあるフォルダのパス)"),
                        Redirect$new("/index.r"))
s$add(name="brewApp", app=brew.app)
s$browse("brewApp")

brewで書いたアプリを登録するためのスクリプトです。Brewery$new()の中のrootだけ書き換えてください。Builder,Brewery,Redirectはgenerator objectで(以下略。Builderオブジェクトはウェブアプリ本体で,直接関数をRhttpdに突っ込むより柔軟に設定ができます。まずStaticオブジェクトで静的なコンテンツの場所を設定します。rootは静的なコンテンツの置かれている実際のフォルダを指定します。urlsはrootの中からウェブアプリ内で用いるフォルダを文字列ベクトルで指定します。少しややこしいですが,例えばstaticというフォルダ内にcssjavascript,imagesというフォルダを作っている場合,rootにstaticを指定して,urlsにc("/css", "/javascript", "/images")を指定します。この設定により,前述の"/plots/hist_9654f.png"へのアクセスは"C:\\Users\\masahiro\\AppData\\Local\\Temp\\RtmpUzjlma/plots\\hist_9654f.png"に読み替えられることになります。

次にBreweryオブジェクトでbrewファイルを読み込みます。root内のファイルがbrewファイルとして認識されます。urlにはStaticと同じくウェブアプリに読み込むファイルを指定します。"/"としておけばroot内のファイルが全てbrewファイルとして認識されます。正規表現を使うこともできるようです。

Redirectオブジェクトでは,ウェブアプリのURL(例えばhttp://127.0.0.1:21483/custom/brew/)にアクセスしたときに転送するURLを指定します。

ウェブアプリの公開

一般公開する場合,R内蔵のウェブサーバーがどの程度のものか分からないので,rapacheとの連携を待ったほうがよいような気がします。ただ,内部向けに公開したい場合もあるでしょう。その場合は,RhttpdオブジェクトのstartメソッドでIPアドレスとポートを設定できます。

s$start(listen="192.168.11.15", port="8080")

読むべき資料

ヘルプがけっこう充実しています。

  • help(Rhttpd)
  • help(Response)
  • help(Request)
  • help(Builder)
  • help(Brewery)
  • help(URLMap)

作者のブログ。この人,パッケージ作りまくってますね…すごい…。http://goo.gl/AJKV2

わからなかったこと

Rをサービスとして待機させておくことってできないんでしょうか? R --file=script.Rとかやっても,scriptを実行したら終了してしまう…。コンソール開きっぱなしにしておくのもスマートじゃないですし…。やっぱりrapache待ちでしょうか。でもrapacheを使うとなるとRookのお手軽さがなくなってしまうのが残念。

sessionInfo

> sessionInfo()
R version 2.13.0 (2011-04-13)
Platform: i386-pc-mingw32/i386 (32-bit)

locale:
[1] LC_COLLATE=Japanese_Japan.932  LC_CTYPE=Japanese_Japan.932    LC_MONETARY=Japanese_Japan.932 LC_NUMERIC=C                  
[5] LC_TIME=Japanese_Japan.932    

attached base packages:
[1] tools     stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] Rook_1.0-2 brew_1.0-6

*1:formタグでactionを指定しない場合,元のページにリクエストを送る,というのを今回知りました…

*2:ちなみにRookのパッケージ環境にあるUtilsはgenerator objectではなくUtilsクラスのオブジェクトそのものです。Rhttpd,Response,Requestはgenerator objectです。

memiscGUIパッケージあらためRzパッケージをCRANに登録しました



これは古い情報です。最新の情報はこちらになります(2012/02/03)









長らく更新が止まっていたmemiscGUIパッケージですが,Rzパッケージと名前を変え,CRANに登録しました。パッケージの目的や使い方には大きな変化は無いですが,更新点は多数あります。

f:id:phosphor_m:20110405182642p:image

  • R5リファレンスクラスで全面的に書き直しました。
  • データセットとデータフレームのリンクが不要に。大局的環境のデータフレームとシームレスに同期。
  • 一度読み込んだデータセットはキャッシュしておくことで,データセットの切替を高速化(ほぼ待ち時間なし)。
  • 独自形式データ(*.rzd)の保存・読み込み機能(実態は独自クラスオブジェクトをsave()で保存しているだけ)。
  • 変数名,変数ラベルの検索機能を実装。
  • リコード時に変数ラベルの付与が可能に。
  • その他,たくさんのバグフィックス

自分でいうのもなんですが,memiscGUIと比べると,格段に使いやすくなり,実用的になっていると思います。

インストール

Rのバージョンは2.12以上に上げる必要があります。バージョンが古い場合はCRANミラーからダウンロードしてインストールしてください。

次に,メニューバーからRzパッケージをインストールするか,install.packages("Rz")を実行してください(現時点ではまだtsukubaミラーには同期されていないので,hyogoミラーからインストールしてください)。他の依存するパッケージがインストールされていない場合は,同時にインストールされます。

これまでにRGtk2パッケージをインストールしていない場合

Rzパッケージが依存しているRGtk2パッケージを使うためには,R外部のソフトウェアとしてGtk+2 Runtimeをインストールする必要があります。Runtimeをインストールしていない場合は,まずlibrary(RGtk2)を実行してください。エラーが出ますが,そのあとにRuntimeをインストールするかどうかを尋ねるダイアログが表示されるので,インストールを選ぶと自動的にダウンロードされ,インストールのプロセスが始まります。特に設定を変えずにそのままインストールすれば大丈夫です。Runtimeのインストール後,Rを再起動し,library(RGtk2)を実行してエラーが出なければ完了です。まだエラーが出る場合は,パソコンを再起動してみてください。

すでにRGtk2パッケージをインストールしている場合

RGtk2のバージョンは2.20以上が必要です。RGtk2パッケージのアップデートはメニューバーからパッケージの更新を実行するか,update.packages()を実行してください。

また,Windowsでは,RGtk2パッケージのアップデートにともない,Gtk2 Runtimeも新しくインストールする必要があります。古いRGtk2が対応していたRuntimeは更新が止まってしまったため,コントロールパネルから必ずアンインストールしてください。アンインストール後,上記の手順で新しいRuntimeをインストールしてください。

起動

library(Rz)で読み込み,Rz()を実行するだけです。Windowsの場合は,メニューバーからも起動できます。詳しい使い方はそのうちマニュアルを書こうと思います。

名前の由来

名前については…特に意味はありません。Rの文字で始めたかったというのと,CRANを調べたところ,2文字目がzのパッケージがひとつも無かったので。

R5 reference class覚え書き

R2.12から実装されたR5クラスの覚え書きです。日本語の情報はまだないみたいなので一番乗りを狙って。ちなみに継承関係は、面倒なので完全に無視してます。ごめんなさい。その他、網羅性は期待しないでください。

R5クラスの特徴

  • 参照渡し
  • オブジェクトを編集できる
  • S4より緩くて使いやすい

他にもあると思いますが、触ってみた感想ということで。

とりあえずクラス定義

tc <- setRefClass(Class="testClass",
        fields = list(f1="numeric", f2="numeric"),
        methods = list(
          foo = function() {
            "f1とf2を足し合わせるメソッド"
            f1 + f2
          }))

R5クラスはsetRefClass()で定義します。主に使うオプションは、Classとfieldsとmethodsです。Classでクラス名を定義します。fieldsにはオブジェクトの持つフィールドの名前を文字列ベクトルかリストで定義します。リストの場合は、list(名前="クラス",...)という形で、フィールドのクラスを定義します。リストを使ってフィールドのクラスを定義した場合、オブジェクトの作成時に型チェックが行われます。型が合わない場合はエラーではなく強制変換されます。たとえ強制変換の結果がNAであっても(警告は出るものの)オブジェクトは生成されるので、注意が必要です。文字列ベクトルで定義した場合は当然型チェックは行われません。methodsにはメソッドをリストで定義します。関数定義の一行目に文字列を書くと、メソッドのヘルプと認識されます。メソッド内では、フィールドに定義した変数を自由に使うことができます。

setRefClass()はgenerator objectを返します。generator objectは、オブジェクトを生成したり、メソッドをクラスに追加するときなどに使います。ちなみにgenerator objectもR5クラスです。

generator object

generator objectはクラスを管理するために使われるので、結構重要です。

test <- tc$new(f1=1, f2=2)
tc$accessors("f1", "f2")
tc$help(foo)
tc$methods(
  bar=function(x){
    f1 <<- f1 + x
  })

前述のとおり、generator object自身もR5クラスなので、各種メソッドを持っています。R5クラスのメソッドは、オブジェクト$メソッド(...)という形で使います。1行目は、newメソッドでオブジェクトを生成しています。accessorsメソッドは、フィールドのgetterとsetterを自動的に定義してくれます。これを実行すると、getF1、setF1、getF2、setF2というメソッドが追加されます。helpメソッドは、メソッドのヘルプを見るためのメソッドです。methodsメソッドは、引数なしで実行した場合はメソッドの一覧を表示します。上の例のようにすれば、メソッドを新たに追加することができます。ちなみに、例のようにメソッド内で<<-を使うことで、フィールドを上書きすることができます。

オブジェクトをいじってみる

> test$foo()
[1] 3
> test$bar(10)
> test$getF1()
[1] 11
> test$foo()
[1] 13
> test$setF2(5)
> test$foo()
[1] 16

その他重要なこと

  • initializeメソッドはオブジェクトを生成するときに実行される
  • finalizeメソッドはガベージコレクションによってオブジェクトが消去される前に実行される
  • メソッド内で、.selfはオブジェクト自身を意味する
  • R5クラスオブジェクトを参照渡しでなくコピーしたいときは、copyメソッド

gnupack 4.08aでessが起動できない。

gnupack 4.08aでessのロケールの問題は解決されたのですが,そもそも起動ができません。
gnupackのconfig.ini内のSHELL=/usr/bin/bashをSHELL=bashにすることでいちおう回避はできました。
ただ,そもそも4.08aでのバグフィックスとしてSHELL=bash→SHELL=/usr/bin/bashに変更になっているので,根の深い問題なのかもしれません…。

追記:
作者さんに問い合わせたところ,適切な対応策を教えていただけました。
SHELL = %CYGWIN_DIR%/bin/bash
と書いておくのがよいようです。次期リリースでこの修正を適用していただけるそうです。感謝。

WindowsでEmacs+ESSでlocaleが設定できない問題の解決方法

追記:この問題は,gnupack4.08aでは起きません。ただし,別の問題が発生しています

id:Rion778さんがWindowsでのESSの設定について素晴らしい記事を書いてくれました。

http://d.hatena.ne.jp/Rion778/20100920

僕もgnupackを使っているのですが,再インストールを繰り返しているうちに設定がめんどくさくなって放置しています…。

さて,id:Rion778さんがヘルプを出されている点について,以前同じところでつまずいて,解決できていたような気がしたので(しかしその方法は放置している間にすっかり忘れていた)記事にしておきます。

ところでこれまでの方法で設定するとR起動時に「起動準備中です - 警告メッセージ: Setting LC_CTYPE=ja_JP.cp932 failed」などと出たうえ、plotなどでR Graphicsデバイスを呼び出したときにメニューが文字化けしてしまうので誰か助けて下さい><

これはどうもこの問題のようなのですが,何をいっているのかさっぱりわかりません。Sys.setlocaleが失敗することがあるという問題のようです(そしてRのバグではなくWindowsの問題らしい)。ESSのパッチを提供するのがスマートなんだと思いますが,LISPわかりません。とりあえずその場しのぎの解決策です。普通に

Sys.setlocale(locale="CP932")

とやってもエラーが返ってきます。いろいろと試してみたところ,

Sys.setlocale(locale="Japanese")

もしくは

Sys.setlocale()

でlocaleを変更できました。後者ですが,引数localeの初期値は""(空の文字列)となっていて,そのまま実行すると,システムのデフォルトのlocaleを設定する,という動作になる,とhelpに書いてました。下の二つのどちらかを.Rprofileに書き足しておけば毎回自動で設定されます。Sys.setlocale(locale="Japanese")のほうが安全かもしれません。

あと,僕の環境ではinit.elに以下の設定が必要でした。

(setq ess-pre-run-hook
  '((lambda () (setq default-process-coding-system '(cp932 .   cp932))
   )))