J's blog

趣味で統計•データ解析をしています

foreachでprogressbarを表示する

最近、pforeachという簡単に並列処理を行うパッケージが僕の中で話題です。とても便利です。

R で超簡単に並列処理を書けるパッケージ pforeach を作った - ほくそ笑む

ただpforeachを使って並列化できるのは良いのですが、並列処理だと普通にやってもprogressbarが表示できません。(僕が知らないだけかもしれないですが)

普通にやるとこうなります。

pb <- txtProgressBar(min = 0, max = 10, style = 3)
pforeach(i = 1:10) ({
  setTxtProgressBar(pb, i)
  sqrt(i)
})
##  [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427
##  [9] 3.000000 3.162278

ダメですね...



ということで考えました。案としては2つです。ループの前に出すか後に出すか。

案1 ループの前に出す

iteratorsパッケージの関数をいじります。iterators::icount関数に関しては、各ループの実行時にイテレータに渡す値を生成して返しています。だったら一緒にprogressbarの表示も一緒にやらせよう、といった発想です。

以下に、iterators::icount関数に少し手を加えたpb_icount関数を作成したのでこれを示します。

pb_icount <- function (count)
{
  if (missing(count))
    count <- NULL
  else if (!is.numeric(count) || length(count) != 1)
    stop("count must be a numeric value")
  i <- 0L
  pb <- txtProgressBar(min = 0, max = count, style = 3)  # 追加
  nextEl <- function() {
    if (is.null(count) || i < count) {
      setTxtProgressBar(pb, i+1)  # 追加
      (i <<- i + 1L)
    }
    else {
      cat("\n")  # 追加
      stop("StopIteration", call. = FALSE)
    }
  }
  it <- list(nextElem = nextEl)
  class(it) <- c("abstractiter", "iter")
  it
}

これを使ってもう一度並列処理をやってみます。

N <- 10000
pforeach(i = pb_icount(N)) ({
  sqrt(i)
})[1:10]
  |========================================================================| 100%
##  [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427
##  [9] 3.000000 3.162278

やりました!!
ただ100%になってからちょっと間があるのが気になる所です。もしかしてループの割当を先に行っていたりするのでしょうか。そうであればこの案はボツなんですが。


他にはiterators::iter関数はなかなか複雑で難しそうではありますが、こちらでもできればやりたいですね。
その他の関数については、iterators::icount関数と似た構造であれば同様にできるのではないでしょうか。

案2 ループの後に出す

イテレータではなく、foreach関数の引数.combineに注目してprogress barを表示させている例がありました。

How do you create a progress bar when using the "foreach()" function in R? - Stack Overflow

これをもとにして、以下にpb関数を作成しました。(08/16修正)

pb <- function(N, FUN = c) {
  pbar <- txtProgressBar(min = 2, max = N, style = 3)
  count <- 0L
  if(is.character(FUN)) {
    FUN <- get(FUN)
  }
  function(...) {
    count <<- count + length(list(...)) - 1L
    setTxtProgressBar(pbar, count)
    if(count == (N-1)) {
      cat("\n")
    }
    FUN(...)
  }
}

では検証します。.combinec関数でなくなるので、引数.multicombineも付けておきます。

N <- 10000
pforeach(i = 1:N, .combine = pb(N), .multicombine = TRUE) ({
  sqrt(i)
})[1:10]
  |=======================================================================| 100%
##  [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427
##  [9] 3.000000 3.162278

やりました!!
でも実は.multicombine=TRUEにしたことでループの回数が変化しているので、ループ回数を表すprogressbarにはならずcombine回数となっています。しかし進捗状況を表すという目的であれば十分だと思います。



速度比較

最後に速度比較です。

  • progressbarなし
  • 案1
  • 案2

を比較します。

# progressbarなし
system.time({
  N <- 10000
  pforeach(i = icount(N)) ({
    sum(rnorm(10000))
  })[1:10]
})
##   user  system elapsed 
## 27.616   1.214  11.974 
# 案1
system.time({
  N <- 10000
  pforeach(i = pb_icount(N)) ({
    sum(rnorm(10000))
  })[1:10]
})
  |========================================================================| 100%
##   user  system elapsed 
## 16.797   0.945  12.455 
# 案2
system.time({
  N <- 10000
  pforeach(i = icount(N), .combine = pb(N)) ({
    sum(rnorm(10000))
  })[1:10]
})
  |========================================================================| 100%
##   user  system elapsed 
## 24.370   1.107  12.799



まとめ

案1(ループの前に出す)

  • あらかじめ関数を作成する必要がある
  • まだicount関数しか対応できていない
  • 100%になってから完了まで少し間がある

案2(ループの後に出す)

  • 引数にループ回数が必要
  • 任意の関数に適用できる
  • progressbarの進行速度が適切そう(これについては実行して確認してみてください)
  • .multicombine=TRUEにすると(.maxcombineを変化させると)progressbarの回数に影響する

.multicombineでcombine回数が変化することで、案2ではループ回数を表すprogressbarにはなりませんが、進捗状況を表すという意味では案2は十分に良い策だと思います。

きちんとループ回数にしたいという場合は案1を使っていくしかないかもしれないです。

LC_ALL=Cでsortを高速に

bashのsortがLC_ALL=Cにすると速くなるという記事を見つけました(割と有名なようで)。

ちょっとbashで試してみます。 一応sortのversionも晒しておきます。

$ sort --version
sort (GNU coreutils) 5.93
Copyright (C) 2005 Free Software Foundation, Inc.
This is free software.  You may redistribute copies of it under the terms of
the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.
There is NO WARRANTY, to the extent permitted by law.

Written by Mike Haertel and Paul Eggert.

それでは、10桁の英数字列を100万個生成し、それをソートして比較します。

$ cat /dev/urandom | LC_ALL=C tr -dc '[:alnum:]' | fold -w 10 | head -n 1000000 > words.txt
$ time LC_ALL=C sort words.txt > /dev/null

real    0m0.751s
user    0m0.712s
sys     0m0.039s
$ time LC_ALL=ja_JP.UTF-8 sort words.txt > /dev/null

real    0m16.918s
user    0m16.820s
sys     0m0.076s
$ time LC_ALL=en_US.UTF-8 sort words.txt > /dev/null

real    0m17.057s
user    0m16.915s
sys     0m0.093s

なぜLC_ALL=Cにすると速くなるかというと、バイト列でソートしてくれるからだそうです。でもそのせいで並び順も変わってしまう点には注意が必要です。

Rで試した

Rのsort関数も同じような挙動をするのでしょうか。気になります。

dat <- read.table("words.txt")[[1]]
bench <- function(lc_all) {
  Sys.setlocale(locale = lc_all)
  time <- proc.time()
  assign(lc_all, sort(dat), pos = globalenv())
  proc.time() - time
}

bench("C")
##    user  system elapsed 
##   5.850   0.042   7.430 
bench("ja_JP.UTF-8")
##    user  system elapsed 
##  19.240   0.085  20.647 
bench("en_US.UTF-8")
##    user  system elapsed 
##  19.081   0.076  20.366 

しっかり差がついています。

all(get("C") == get("ja_JP.UTF-8"))
## [1] FALSE
all(get("C") == get("en_US.UTF-8"))
## [1] FALSE
all(get("ja_JP.UTF-8") == get("en_US.UTF-8"))
## [1] TRUE

ただutf-8かどうかで並び順は変わってしまうようですね。

ちなみにこちらはdplyrのarrange関数を使って比較した場合です。

bench2 <- function(lc_all) {
  Sys.setlocale(locale = lc_all)
  time <- proc.time()
  assign(lc_all, arrange(as.data.frame(dat), dat)[[1]], pos = globalenv())
  proc.time() - time
}

bench2("C")
##    user  system elapsed 
##   2.406   0.013   2.722
bench2("ja_JP.UTF-8")
##    user  system elapsed 
##   2.877   0.020   3.294 
bench2("en_US.UTF-8")
##    user  system elapsed 
##   2.325   0.012   2.602 

as.data.frameをはさんでもこの速さ。さすがの羽鳥氏です。

all(get("C") == get("ja_JP.UTF-8"))
## [1] TRUE
all(get("C") == get("en_US.UTF-8"))
## [1] TRUE
all(get("ja_JP.UTF-8") == get("en_US.UTF-8"))
## [1] TRUE

速度がほとんど変わらないように、結果も変わらない。安心です。

Pythonでlightblueを入れたときのなぐり書き

bluetooth通信をMacでするために必要なlightblueを入れる際に苦労したのでメモ。

環境

第一の壁

公式サイトから落としたファイルでやったときも試しましたが、そっちのエラーの方がイミフで解決不能だったので、githubから落とします。
適当なフォルダに移動してファイルを落としましょう。

$ cd python/tmp
$ git clone https://github.com/0-1-0/lightblue-0.4

解凍します。

$ tar xzvf lightblue-0.4.tar.gz

解凍したら、そのフォルダに移動して、とその前に必要なパッケージを入れないといけません(どこかに書いてあったような気がするだけでいらないかもしれないです)。

$ pip install pyobjc

をしておいて下さい。 それでは

$ sudo python setup.py install

でインストールです。本来ならこれでうまくいくとおもうのですが、僕の場合はだめでした。
こんな具合でエラーが出れば僕と一緒です。

$ sudo python setup.py install
running install
running build
running build_py
running install_lib
running install_egg_info
Removing /***/***/***/lib/python2.7/site-packages/lightblue-0.4-py2.7.egg-info
Writing /***/***/***/lib/python2.7/site-packages/lightblue-0.4-py2.7.egg-info
Build settings from command line:
    ARCHS = $(NATIVE_ARCH_ACTUAL)
    DEPLOYMENT_LOCATION = YES
    DSTROOT = /
    INSTALL_PATH = /Library/Frameworks

=== BUILD TARGET LightAquaBlue OF PROJECT LightAquaBlue WITH CONFIGURATION Release ===

Check dependencies
No architectures to compile for (ARCHS=$(NATIVE_ARCH_ACTUAL), VALID_ARCHS=i386 x86_64).

** INSTALL FAILED **


The following build commands failed:
    Check dependencies
(1 failure)

正しい解決策かどうかわかりませんが、やってみたことを羅列しておきます。

  • setup.pyのline66の-arch 'x86_64'を取り除く
  • src/mac/Lightblue/Lightblue.xcodeprojをXcodeで開き、Build SettingのArchitecturesのReleaseをppc i386からNative Architecture of Build Machine (x86_64)に変更
  • 同じファイルについて、Build SettingのValid Architecturesをi386 x86_64からx86_64のみに変更

これをしたら再び、

$ sudo python setup.py install

と唱えます。さあimport lightblueと入力してみましょう!
何も言われなければ成功です!僕の場合はだめでした!

第二の壁

さてさきほどのコマンドでエラーがでてしまった僕ですが、エラー内容は以下のとおりです。

>>> import lightblue
/***/***/***/lib/python2.7/site-packages/objc/_bridgesupport.py:643: RuntimeWarning: Error parsing BridgeSupport data for IOBluetooth: PyObjCRT_SkipTypeSpec: Got '0x29' at end of union encoding, expecting '0x29'
  warnings.warn("Error parsing BridgeSupport data for %s: %s" % (frameworkName, e), RuntimeWarning)

ただこのバグは解決済みのようでした。しかしpipで入れたもの(3.0.4)は解決前でしたので、 https://bitbucket.org/ronaldoussoren/pyobjcから最新のものを落としましょう。
そんでもってディレクトリ移動してインストール。

$ cd ronaldoussoren-pyobjc-**********/pyobjc-core
$ sudo python setup.py install

エラーも吐かずに終えたので、import lightblueを試したところ、無事に成功しました。

foreachについてまとめたい

foreachパッケージのforeach関数についてです。 Rで並列処理を行う際に今まで使用してきましたが、引数は.combineをいじるくらいでした。他にも%dopar%とかよくわからないものを蔑ろにしていました。この関数は今後もよく使うことになりそうなので、頑張ってまとめてみたいと思います。

基本

基本形としてはこんな感じです。

foreach(i = 範囲) %do% {
  -- 処理 --
}

1~3の平方根の計算例が以下になります。返り値はデフォルトでlistです。

> foreach(i = 1:3) %do% { sqrt(i) }
[[1]]
[1] 1

[[2]]
[1] 1.414214

[[3]]
[1] 1.732051

イテレータをここではiとしていますが、もちろんaでもbでもokです。さらに言えば、イテレータは2つ以上あっても大丈夫で、その場合は要素数の少ない方に合わせて終了します。

> foreach(a = 1:1000, b = rep(10, 2)) %do% { a + b }
[[1]]
[1] 11

[[2]]
[1] 12

%do% と %dopar%

%do%%dopar%の違いは並列処理をするかどうかです。

%do%    : 並列処理なし
%dopar% : 並列処理あり

本エントリは並列処理ではなく、あくまでもforeachの使い方としたいので、%do%を用いていきます。(一部除く)

また余談ですが、並列化する/しないをうまく書き分ける話があるみたいです。



引数

.combine

これは返り値に関する引数です。正確には、返り値を生成する関数を指定する引数でしょうか。crbindなどの関数を指定することでベクトルや行列で返り値を構成することができます。指定しないとlist形式で返されます。

> foreach(i = 1:3, .combine = c) %do% { exp(i) }
[1]  2.718282  7.389056 20.085537
> foreach(i = 1:3, .combine = rbind) %do% { rnorm(3) }
               [,1]      [,2]       [,3]
result.1 -0.6189962  1.081181  0.8920234
result.2 -0.6117038  1.418355  0.1769341
result.3 -0.4464221 -0.357747 -0.9254935

少し応用で、以下のような用法もあります。

> foreach(i = 1:3, .combine = sum) %do% { i^2 }
[1] 14

.combineの関数はこの記事によるとReduce関数をイメージすると良いとのことです。(Reduce関数についてはこの記事がわかりやすかったです)

.init

この引数で指定した値が返り値の初めに入ります。その際.combineを指定しないとエラーが出ます。若干わかりにくいかもしれません。以下に例を示します。

foreach(i = 1:5, .combine = c) %do% { i+10 }
## [1] 11 12 13 14 15
foreach(i = 1:5, .combine = c, .init = 1:2) %do% { i+10 }
## [1]  1  2 11 12 13 14 15

先ほど述べたように、そのまま最初に加わります。 rbindなど行列で返すと行(列)名が異なるようです。

foreach(i = 1:5, .combine = cbind, .init = 1:4) %do% { rnorm(4) }
##      accum   result.1   result.2    result.3   result.4    result.5
## [1,]     1 -0.6002596 -1.0264209 -0.34754260 -1.6679419  0.60796432
## [2,]     2  2.1873330 -0.7104066 -0.95161857 -0.3802265 -1.61788271
## [3,]     3  1.5326106  0.2568837 -0.04502772  0.9189966 -0.05556197
## [4,]     4 -0.2357004 -0.2466919 -0.78490447 -0.5753470  0.51940720

.final

これは、繰り返し処理後にかける関数を指定します。デフォルトではNULLですので何もかけません。単純に次にかける処理をここに指定しても良いと思います。 例えば、ベクトルで返ってきた正規乱数の和をとってみましょう。まず和をとる前が、以下です。

# 10個の正規乱数
set.seed(123)
foreach(i = 1:10, .combine = c) %do% { rnorm(1) }
##  [1] -0.56047565 -0.23017749  1.55870831  0.07050839  0.12928774  1.71506499
##  [7]  0.46091621 -1.26506123 -0.68685285 -0.44566197

これらの和をとります。和をとるだけなら色々な方法がありますので、複数示します。

set.seed(123)
foreach(i = 1:10, .combine = c, .final = sum) %do% { rnorm(1) }
## [1] 0.7462564
set.seed(123)
sum(foreach(i = 1:10, .combine = c) %do% { rnorm(1) })
## [1] 0.7462564
set.seed(123)
foreach(i = 1:10, .combine = sum) %do% { rnorm(1) }
## [1] 0.7462564

どれも正しく同じ答えが出ています。しかし、繰り返し数を増やしてみると3番目の方法がやや時間がかかるようになります。これについては.combineのところで記述したReduce関数をイメージすることができるとよくわかります。簡単に言えば、1番目と2番目は最後に一度関数をかけただけ、一方3番目は繰り返しのたびにsum関数をかけているという違いがあり、その分だけ処理が増えています。

.inorder

これは返り値を正しい順番で返すかどうかの引数です。もう少し正確に言うと、正しい順番であることを保証するかどうかの引数です。デフォルトでは、順番を保証する(TRUE)となっています。

では"正しい順番"とはどういうことでしょう。ここでの"正しい"は、イテレータが指定した順番通りに結果が返ってくることです。これが保証されるのがTRUEの時、保証されないのがFALSEの時です。FALSEの時の順番を、仮に"素直な順番"とします。パラレルに計算が行われると、複数の処理が別々に行われます。しかしその処理時間はそれぞれで異なる場合があり、その処理が終わった順に並べられた時、これが"素直な順番"になります。"素直な順番"は、偶然"正しい順番"になることもあるので、そういった意味で保証されません。 詳しい説明はUsing The foreach Packageを参照下さい。

正しい順番であるかどうかが重要でない場合や、.combine='+'とした時のように順番が影響しない場合は、この引数をFALSEにすることで少しだけ処理が速くなるようです。以下に簡単な処理を100回ほど繰り返してsummaryで比較しました。

require(doMC)  # ここは各自適切なものを
registerDoMC(detectCores())  # 実行時は4コア
bench1 <- bench2 <- numeric(100)
for(i in 1:100) {
  bench1[i] <- system.time(foreach(j = 1:1000, .combine = '+', .inorder = T) %dopar% { sum(rnorm(100)) })[3]
  bench2[i] <- system.time(foreach(j = 1:1000, .combine = '+', .inorder = F) %dopar% { sum(rnorm(100)) })[3]
}
summary(bench1)  # .inorder = TRUE
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.5300  0.5550  0.5650  0.5838  0.5920  0.7520 
summary(bench2)  # .inorder = FALSE
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.4850  0.5050  0.5170  0.5286  0.5420  0.7200

FALSEにした方がやはり多少速くなっていることがわかります。ちなみに並列処理にしなくても(%do%でも)少し速くなります。

.multicombine

これ、注目です。 この引数は.combine引数で与えた関数の引数が3個以上受け取れる時にTRUEにすることで活躍します。デフォルトでは関数の引数の数が3個以上受け取れない場合を考えているのでFALSEです(c,cbind,rbindは例外で、3個以上受け取りが可能なことがわかっているので自動でTRUEになります)。受け取れないのにも関わらずTRUEにしてしまうとエラーが大量発生するので注意です。

受け取り可能かどうかがわからない場合は、args関数をその関数にかけてみるとよくわかります。 例えば以下です。

args(c)
## function (..., recursive = FALSE) 
## NULL
args(append)
## function (x, values, after = length(x)) 
## NULL

c関数の引数は...であり、引数にした値全部を結合してくれます。一方でappend関数の引数はxvaluesであり、xの後ろにvaluesを結合します。つまり、同様の処理をすることを考えると、c関数は引数を3個以上受け取ることが可能であり、append関数は引数を2個までしか受け取ることができません。

さて、これによって何が良いかというと、適宜TRUEにしてあげることで処理が速くなります。 以下で、100個の正規乱数の和を1000個計算し、その和をとるという処理について速度比較をしました。

bench1 <- bench2 <- numeric(100)
for(i in 1:100) {
  bench1[i] <- system.time(foreach(j = 1:1000, .combine = sum, .multicombine = T) %do% { rnorm(100) })[3]
  bench2[i] <- system.time(foreach(j = 1:1000, .combine = sum, .multicombine = F) %do% { rnorm(100) })[3]
}
summary(bench1)  # .multicombine = TRUE
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.3500  0.3680  0.3920  0.4388  0.4290  1.2000 
summary(bench2)  # .multicombine = FALSE
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.5230  0.5535  0.6000  0.6630  0.6665  1.7420 

結構速くなりましたね。パフォーマンス向上についてはforeach の .multicombine 引数について #rstatsj - Qiitaにもありましたので、そちらも御覧ください。

.maxcombine

.combine引数で与えた関数の引数の最大個数を指定します。.multicombine = FALSEの時(デフォルト)では2になり、.multicombine = TRUEの時は100になります。

.errorhandling

エラー発生時の動作を示す文字列オブジェクトを指定します。選択肢は以下になります。

'stop'   : エラーが発生すると処理を停止する
'remove' : エラーが発生したイテレーションの処理結果を省く
'pass'   : エラーが発生したイテレーションの処理結果をエラーオブジェクトとして返す

デフォルトではstopです。

.packages

別のパッケージの関数を用いる場合、この引数にそのパッケージ名を文字列ベクトルとして指定します%do%の時は省略可能です。ただ以下に示す例では外しても僕の環境では実行できるのでどう影響があるのかはわかりません。

x <- matrix(runif(500), 100)
y <- gl(2, 50)
require(randomForest)
# %do% の場合
foreach(ntree = rep(250, 4), .combine = combine) %do% {
  randomForest(x, y, ntree = ntree)
}
## 
## Call:
##  randomForest(x = x, y = y, ntree = ntree) 
##                Type of random forest: classification
##                      Number of trees: 1000
## No. of variables tried at each split: 2
## 

# %dopar% の場合
foreach(ntree = rep(250, 4), .combine = combine, .packages = 'randomForest') %dopar% {
  randomForest(x, y, ntree = ntree)
}
## 
## Call:
##  randomForest(x = x, y = y, ntree = ntree) 
##                Type of random forest: classification
##                      Number of trees: 1000
## No. of variables tried at each split: 2
## 

.export

この引数で、オブジェクト名を文字列ベクトルで指定します。ここで指定するのは、現在の環境で定義されていないオブジェクトを指定します。 この引数で、現在の環境で定義されていないオブジェクトを文字列ベクトルで指定し、使用可能にします。デフォルトはNULLです。

foreachパッケージで並列化する時、現在の"環境"にない変数・関数は、明示的に.export引数にて指定しなければならない - My Life as a Mock Quant

この記事によれば、上の環境のオブジェクトも.exportしないといけないとありましたが、これまたどうも僕の実行環境ではexport必要なしにできました。どういうことなんでしょう。

.noexport

この引数は、上記の.exportでオブジェクトを使用可能にしてしまったせいで、同名のオブジェクトに影響が出てしまう際に用います。.exportで例えばls()として一度にexportした場合、余計なものまでexportされてしまうので、この引数で除外するオブジェクトを文字列ベクトルで指定します。

.verbose

これをTRUEにすることで、繰り返しの詳細を出力することができます。エラーが発生した場合に使ってみると良いでしょう。デフォルトはFALSEです。

foreach(i = 1:2, .combine = c, .verbose = TRUE) %do% { i }
## evaluation # 1:
## $i
## [1] 1
## 
## result of evaluating expression:
## [1] 1
## got results for task 1
## numValues: 1, numResults: 1, stopped: FALSE
## returning status FALSE
## evaluation # 2:
## $i
## [1] 2
## 
## result of evaluating expression:
## [1] 2
## got results for task 2
## numValues: 2, numResults: 2, stopped: FALSE
## returning status FALSE
## numValues: 2, numResults: 2, stopped: TRUE
## first call to combine function
## evaluating call object to combine results:
##   fun(result.1, result.2)
## [1] 1 2



when

ヘルプマニュアルの同ページに記載されていたのでついでに。 これは%:%演算子と組み合わせてif文のように利用することができます。例えば以下。

set.seed(123)
foreach(a = rnorm(10), .combine = c) %do% sqrt(a)
##  [1]       NaN       NaN 1.2484824 0.2655342 0.3595660 1.3096049 0.6789081
##  [8]       NaN       NaN       NaN
Warning messages:
1: In sqrt(a) : NaNs produced
2: In sqrt(a) : NaNs produced
3: In sqrt(a) : NaNs produced
4: In sqrt(a) : NaNs produced
5: In sqrt(a) : NaNs produced

これは10個の正規乱数について「平方根を求めベクトルで返す」という処理になっています。当然負の実数(numeric)に対し平方根をとることはできないので、NaN及び警告メッセージが出ています。これに対して、whenを用いた例が以下になります。

set.seed(123)
foreach(a = rnorm(10), .combine = c) %:% when(a >= 0) %do% sqrt(a)
## [1] 1.2484824 0.2655342 0.3595660 1.3096049 0.6789081

whenによって、これは10個の正規乱数について「aが0以上ならば平方根を求めベクトルで返す」という処理になっています。 条件を満たしたものがどれかがわからないので、あまり使う機会はなさそうです。



速度比較

並列処理を指定しているわけではないですが、通常のfor文との速度比較をしてみたいと思います。雑比較です。

# for文を回します
before <- proc.time()
x <- numeric(10000)
for(i in 1:10000) x[i] <- sum(rnorm(1000))
proc.time() - before
##   user  system elapsed 
##  1.174   0.010   1.261  

# foreach文を回します(工夫なし)
before <- proc.time()
x <- foreach(i = 1:10000, .combine = c) %do% { sum(rnorm(1000)) }
proc.time() - before
##   user  system elapsed 
##  6.664   0.042   6.703

# foreach文を回します(.inorder変更)
before <- proc.time()
x <- foreach(i = 1:10000, .combine = c, .inorder = FALSE) %do% { sum(rnorm(1000)) }
proc.time() - before
##   user  system elapsed 
##  6.447   0.047   6.489

# foreach文を回します(.multicombine変更)
before <- proc.time()
x <- foreach(i = 1:10000, .combine = c, .multicombine = TRUE) %do% { sum(rnorm(1000)) }
proc.time() - before
##   user  system elapsed 
##  4.936   0.036   4.879

# foreach文を回します(.inorderと.multicombine変更)
before <- proc.time()
x <- foreach(i = 1:10000, .combine = c, .multicombine = TRUE, .inorder = FALSE) %do% { sum(rnorm(1000)) }
proc.time() - before
##   user  system elapsed 
##  4.697   0.025   4.702 

やっぱり並列処理をしないとforeachを使う意味はなさそうですね。 工夫点として.inorder.multicombineがありましたが、.inorderは小さい効果、.multicombineは大きい効果が見込めそうです。


参考

MacでRを複数起動する

別にRに限った話ではないのですが、GUIのRを複数起動したいと思ったのでその方法です。


実はすごく簡単で、ターミナルで以下のコマンドを実行するだけで別プロセスでGUIのRが出てきます。

open -n /Applications/R.app

RStudioを複数起動したいならRStudio.appにするだけです。


また、バッチ処理でも複数処理はかけられます。普通にやると邪魔なので、 バックグラウンド上で実行すると良いと思います。 バッチ処理については、この記事がオススメです。

Rをコマンドラインで実行する


以上です。

参考