RubyからRを使うRinRubyを試してみた

Ruby から R へアクセスできる RinRuby について日本語の文章があまりないみたいなので書く。

今回の環境は僕が常用している Mac OS X 10.6.4 と R 2.11.1, Ruby 1.9.2, RubyGems 1.3.7です。Ruby 1.8系でも動きそうですが、まさか、まだ 1.8 系使っているとか、ありえないですよねー、まさかねー。

まず RinRuby をインストールするよ。

sudo gem install rinruby

すごく簡単。適当にあることないことコードを書く。

#!/usr/bin/env ruby

require 'rubygems'
require 'rinruby'
require 'pp'

sample_size = 100
hist_file   = "/Users/itoshi/Projects/rinruby/hist.png"

# うざいメッセージを止める
R.echo(enable = false)

# pull メソッドで R コードの実行結果を受けとる
R.x = R.pull "rnorm(#{sample_size})"

# 関数も問題なく使える
R.x_sum = R.pull "sum(x)"
R.x_sd  = R.pull "sd(x)"

# Ruby で出力する
puts R.x_sum
puts R.x_sd

# 関数のように数行のときはヒアドキュメントを使う
R.eval <<EOF
myfactorial <- function(n) {
  if (n <= 1)
    return(1)
  else
    return( n * Recall(n-1) )
}
fac <- myfactorial(#{sample_size})
EOF

# Rでは fac という変数は、Ruby からは R.fac でアクセスできる
puts R.fac

# plot もできる
# export DISPLAY=localhost:0.0 して X11 の起動を忘れずに (Mac)
R.eval <<EOF
png("#{hist_file}")
hist(x)
dev.off()
EOF

ローカルに hist.png ができているはず。

さてどういう仕組みになっているのだろうか? RinRuby インスタンスを除いてみよう。

irb(main):001:0> require "rinruby"
=> true
irb(main):002:0> require "pp"
=> true
irb(main):003:0> pp RinRuby.new
#<RinRuby:0x00000100a58380
 @echo_enabled=true,
 @echo_stderr=false,
 @engine=#<IO:fd 18>,
 @executable="R",
 @hostname="127.0.0.1",
 @interactive=true,
 @opts=
  {:echo=>true,
   :interactive=>true,
   :executable=>nil,
   :port_number=>38442,
   :port_width=>1000,
   :hostname=>"127.0.0.1"},
 @platform="default",
 @port_number=39093,
 @port_width=1000,
 @reader=#<IO:fd 18>,
 @readline="constant",
 @server_socket=#<TCPServer:fd 15>,
 @socket=#<TCPSocket:fd 16>,
 @writer=#<IO:fd 18>>
=> #<RinRuby:0x00000100a58380 @opts={:echo=>true, :interactive=>true, :executable=>nil, :port_number=>38442, :port_width=>1000, :hostname=>"127.0.0.1"}, @port_width=1000, @executable="R", @hostname="127.0.0.1", @port_number=39093, @server_socket=#<TCPServer:fd 15>, @echo_enabled=true, @echo_stderr=false, @interactive=true, @platform="default", @readline="constant", @engine=#<IO:fd 18>, @reader=#<IO:fd 18>, @writer=#<IO:fd 18>, @socket=#<TCPSocket:fd 16>>

なにやら TCP という怪しい文字列が! RinRuby は 800行程度の Pure Ruby で書かれているようです。のぞいてみましょう。どきどき。

RinRuby のコンストラク部分の抜粋です。

    while true
      begin
        @port_number = @opts[:port_number] + rand(port_width)
        @server_socket = TCPServer::new(@hostname, @port_number)
        break
      rescue Errno::EADDRINUSE
        sleep 0.5 if port_width == 1
      end
    end
    @echo_enabled = @opts[:echo]
    @echo_stderr = false
    @interactive = @opts[:interactive]
    @platform = case RUBY_PLATFORM
      when /mswin/ then 'windows'
      when /mingw/ then 'windows'
      when /bccwin/ then 'windows'
      when /cygwin/ then 'windows-cygwin'
      when /java/
        require 'java' #:nodoc:
        if java.lang.System.getProperty("os.name") =~ /[Ww]indows/
          'windows-java'
        else
          'default-java'
        end
      else 'default'
    end
    if @executable == nil
      @executable = ( @platform =~ /windows/ ) ? find_R_on_windows(@platform =~ /cygwin/) : 'R'
    end
    platform_options = []
    if ( @interactive )
      begin
        require 'readline'
      rescue LoadError
      end
      @readline = defined?(Readline)
      platform_options << ( ( @platform =~ /windows/ ) ? '--ess' : '--interactive' )
    else
      @readline = false
    end
    cmd = %Q<#{executable} #{platform_options.join(' ')} --slave>
    @engine = IO.popen(cmd,"w+")
    @reader = @engine
    @writer = @engine
    raise "Engine closed" if @engine.closed?
    @writer.puts <<-EOF
      #{RinRuby_KeepTrying_Variable} <- TRUE
      while ( #{RinRuby_KeepTrying_Variable} ) {
        #{RinRuby_Socket} <- try(suppressWarnings(socketConnection("#{@hostname}", #{@port_number}, blocking=TRUE, open="rb")),TRUE)
        if ( inherits(#{RinRuby_Socket},"try-error") ) {
          Sys.sleep(0.1)
        } else {
          #{RinRuby_KeepTrying_Variable} <- FALSE
        }
      }
      rm(#{RinRuby_KeepTrying_Variable})
    EOF
    r_rinruby_get_value
    r_rinruby_pull
    r_rinruby_parseable
    @socket = @server_socket.accept
    echo(nil,true) if @platform =~ /.*-java/      # Redirect error messages on the Java platform

Rの起動は、IO.popen, つまりOSのパイプで、Ruby と Rのデータのやり取りは TCP/IPストリーム型接続で行うという面白い仕組みになっています。始めにソケットを用意しています (4行目)。次に、プラットフォームごとに、Rを起動するコマンドを生成してパイプで開いていますね (13-42)。最後に @server_socket.accept で接続を受けとっています。Ruby がサーバ、Rがクライアントということです。

RSRuby など、RとタイトカップリングしているCコードだとメンテが大変で、Windows とか提供されてないよね。だからこういう仕組みにしたんだよ、というのが作者の主張のようです。RinRubyの論文はこちら (PDF)

History.txt をみると Ruby 1.9 への対応がされていたり、最終更新日が2010/05/01 だったりとちゃんとメンテされているっぽいですね。

まとめ

インストールも簡単だし、使い方もそんなに難しくないね。ちょこっとR側の関数を使いたいときに気軽に使えそうな予感。ただ、Ruby 書いているのに、= が <- になるバグが頻出するw

あれ、気付いたら R package じゃなくて Ruby 読んでたわw 連載のほうもぼちぼちのんびりペースでアップしていきます。

Bioconductor には S4 で書かれたコードがどのぐらいあるのか

「シリーズ: 良質なR package のコードを読むよ」の第3回目です。

前回までのあらずじ

前回は BioC のコードを得る方法について書きました。

第2回: Bioconductor のソースコードを得る
第1回: Bioconductor のパッケージについて知る

なにをするか?

どのパッケージを読むのか決めます。S4のパッケージを読みたいのでそれを探します。

S4パッケージを探す

S4 なのでソースコードに setClass があるはず。package/R/*.R のなかに setClass が出てくるコードを探します。

grep setClass */R/*.R |ruby -lane 'puts $_.split(/\//)[0]' |sort -u|less -S
ACME
AffyCompatible
AnnBuilder
AnnotationDbi
ArrayTools
BCRANK
BSgenome
BioMVCClass
Biobase
Biostrings
BiostringsCinterfaceDemo
BufferedMatrix
CALIB
CAMERA
CGHbase
CGHcall
CNTools
Category
ChIPseqR
ChromHeatMap
DEGseq
DESeq
DFP
DNAcopy
DynDoc
EBImage
EBarrays
GEOquery
GGBase
GGtools
GOstats
GSEABase
GeneAnswers
GeneRegionScan
GeneSpring
GeneTraffic
GeneticsBase
GenomeGraphs
GenomicFeatures
GenomicRanges
Genominator
HTqPCR
IRanges
KCsmart
KEGGgraph
LiquidAssociation
MEDME
MLInterfaces
MVCClass
MassArray
MergeMaid
MotIV
PAnnBuilder
PCpheno
PGSEA
PICS
PatientGeneSets
RMAGEML
ROC
RPA
RTCA
RTools4TB
RWebServices
Ratlpow
ReadMatcher
Rgraphviz
Ringo
Rmagpie
RmiR
Rolexa
RpsiXML
Rredland
Rrsat
Rsamtools
Rswub
Rtreemix
Ruuid
SAGx
SLGI
SMAP
SNPchip
SSPA
SamSPECTRAL
ScISI
ShortRead
SpeCond
TargetSearch
XDE
affy
affyILM
affyMvout
affyPLM
affyPara
altcdfenvs
annaffy
annotate
arrayMvout
attract
baySeq
beadarray
beadarraySNP
biocSurvey
biocViews
biomaRt
caFlowQ
cellHTS2
cghMCR
clippda
clusterStab
codelink
cosmo
crlmm
ddCt
domainsignatures
dualKS
edd
edgeR
eisa
eqtlTools
exonmap
externalVector
fabia
flagme
flowClust
flowCore
flowFP
flowFlowJo
flowMeans
flowMerge
flowNorm
flowQ
flowStats
flowUtils
genArise
genefilter
genomeIntervals
girafe
globaltest
graph
hexbin
hopach
hyperdraw
hypergraph
idiogram
imageHTS
limma
lumi
maDB
maigesPack
makecdfenv
marray
metahdep
methylumi
miRNApath
multtest
ncdfExts
oligoClasses
ontoTools
pathRender
pcaMethods
pdInfoBuilder
pgUtils
pint
plateCore
prada
puma
qpcrNorm
rGADEM
rMAT
rdxml
rfcprim
rflowcyt
rnaSeqTests
rsbml
rtracklayer
safe
segmentSeq
seqLogo
siggenes
simpleaffy
smoothMiner
snapCGH
snpMatrix
spkTools
splicegear
stepNorm
tigre
tilingArray
timecourse
tkWidgets
topGO
vsn
widgetTools
xcms
xmapbridge
xps
yaqcaffy

ちなみに、すべてのパッケージ 434 個のうち、197 個が S4 で書かれているようです。このうちのどれかを読んでいきます。

続きます。いいかげんコード読めw

Bioconductor のソースコードを得る

「シリーズ: 良質なR package のコードを読むよ」の第2回目です。

前回までのあらずじ

前回は BioC を読むことに決めて、そのパッケージの基礎知識について書きました。( 第1回: Bioconductor のパッケージについて知る )

なにをするか?

Bioconductor のコードを取得します。BioC のコードは subversion で管理されています。ウェブで閲覧する方法と、subversion で全ソースコードを得る方法を書きます。

ウェブからソースコードをみる

一番簡単にソースコードを見る方法はウェブ経由でソースコードレポジトリを表示することです。ウェブからみれるので iPad の Mobile safari で、ごろごろしながらや電車のなかで、ソースコードが見れるのが良いですね。

https://hedgehog.fhcrc.org/bioconductor/trunk/madman/Rpacks/
ID: readonly, Password: readonly でアクセスできます。ひとつのパッケージがひとつのディレクトリになっています。

そもそも動いているコードを見ないと意味がありません。稼動状況は以下で見ることができます。これはBioC 2.6 の各パッケージの動作状況です。
http://bioconductor.org/checkResults/2.6/bioc-LATEST/

Subversion を使ってソースコードを得る

3Gぐらいあります。以下のコマンドですべてのソースコードを得ることができます。username と password は readonly です。

svn co --username readonly --password readonly https://hedgehog.fhcrc.org/bioconductor/trunk/madman/Rpacks Rpacks-devel

Rpacks-devel ディレクトリにすべてのソースコードがダウンロードされています。こちらもひとつのパッケージがひとつのディレクトリになっています。

特定のパッケージのみ得たい場合は、

svn co https://hedgehog.fhcrc.org/bioconductor/trunk/madman/Rpacks/Biobase

です。例はBioCのベースになるパッケージ Biobase を得ています。

参考: http://bioconductor.org/developers/source-control/

さて、次回からソースコードを読みます。どのパッケージを読みましょうかね?

続きます。